Fernet encryption/decryption with python and kotlin

Viewed 1977

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!

1 Answers

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
}
Related