3

I have a project written in python. I use cryptography library to encrypt and decrypt data. I do it how is shown in their tutorial.

Here is my python code:

import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

password = b"my password"
salt = os.urandom(16)

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),
                 length=32,
                 salt=salt,
                 iterations=100000,
                 backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
data = b"my data..."
token = f.encrypt(data)

Then for decryption I can just use:

f.decrypt(token)

Everything works perfectly in python but now I need to do the same thing in kotlin. I found out about fernet java-8 library but I don't know how to use it in the same way.

The problem is that I have two tools: one is written in python and another I want to write in kotlin. Both tools are meant to do the same thing - the python one is for desktop and the kotlin one is gonna be an android app. So it is really important for their encryption to be the same, so that files encrypted in python (desktop tool) can be decrypted in kotlin (android app) and vice versa.

But I don't know how to write analogous kotlin code.

You see there is a function (or class) called PBKDF2HMAC and there is also base64.urlsafe_b64encode and others. And I don't know what are analogous functions in kotlin or fernet java-8.

So how should I do it? Assuming that in kotlin I have to use password and salt I used in python.

Thanks!

acmpo6ou
  • 840
  • 1
  • 12
  • 21

1 Answers1

2

In Java/Kotlin, using fernet-java8, the token generated with the Python code could be decrypted as follows:

import java.security.SecureRandom
import java.util.Base64
import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory
import com.macasaet.fernet.Key
import com.macasaet.fernet.Token
import com.macasaet.fernet.StringValidator
import com.macasaet.fernet.Validator
import java.time.Duration
import java.time.temporal.TemporalAmount

...

// Data from encryption
val salt = Base64.getUrlDecoder().decode("2Yb8EwpYkMlycHxoKcmHuA==")
val token = Token.fromString("gAAAAABfoAmp7C7IWVgA5urICEIspm_MPAGZ-SyGnPEVUBBNerWQ-K6mpSoYTwRkUt3FobyAFHbYfhNtiGMe_96yyLvUoeLIIg==");

// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)

// Decrypt
val validator: Validator<String> = object : StringValidator {
    override fun getTimeToLive(): TemporalAmount {
        return Duration.ofHours(24)
    }
}
val data = token.validateAndDecrypt(fernetKey, validator)
println(data) // my data...

with:

fun deriveKey(password: String, salt: ByteArray): String {
    val iterations = 100000
    val derivedKeyLength = 256
    val spec = PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength)
    val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
    val key = secretKeyFactory.generateSecret(spec).encoded
    return Base64.getUrlEncoder().encodeToString(key)
}

Here the Fernet key is derived using the key derivation function PBKDF2. PBKDF2 expects various input parameters, such as a password, a digest, a salt, an iteration count and the desired key length. In the posted example the key is returned Base64url encoded.
For decryption the same parameters must be used as for encryption. Since the salt is usually (as in the posted code) randomly generated during encryption, it must be passed to the decryption side along with the ciphertext (note: the salt is not a secret).

The validator sets the time-to-live (by default 60s) to 24h, see here for more details.

In the posted Python code the export of the salt has to be added, e.g. by Base64url encoding it analogous to key and token (and printing it for simplicity). In practice, salt and token could also be concatenated during encryption and separated during decryption.

Update:

The encryption part is analogous:

// Generate salt
val salt = generateSalt()
println(Base64.getUrlEncoder().encodeToString(salt))

// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)

// Encrypt
val data = "my data..."
val token = Token.generate(fernetKey, data)
println(token.serialise()) // the Base64url encoded token

with

fun generateSalt(): ByteArray {
    val random = SecureRandom()
    val salt = ByteArray(16)
    random.nextBytes(salt)
    return salt
}
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thanks a lot! I didn't tested it yet but it looks promising! When I test it, then I will mark your answer as correct one. Also thanks for this: "note: the salt is not a secret", because sometimes I had doubts about storing salt as it is and not in a safe place. – acmpo6ou Nov 02 '20 at 14:02
  • 1
    @Acmpo6ou - Since the question is tagged with Kotlin, I ported the Java code to Kotlin (the Java code can be retrieved from the history if someone needs it). – Topaco Nov 02 '20 at 17:00
  • Thanks for rewriting all code to Kotlin you really helped me! – acmpo6ou Nov 03 '20 at 08:40