Intermittently corrupted data with Google Tink encryption

Viewed 386

Im investigating the use of com.google.crypto.tink:tink-android:1.6.1 in my current Android project.

The data I am encrypting includes the OAuth2 Access Token/Refresh Token I employ for my remote API calls, e.g. Access Token is my Bearer token for the Authorisation HTTP header.

Im concerned I have made an error in my encryption/decryption logic as I am experiencing an intermittent problem where I cannot Refresh the Token. The error from the server

{"error_description":"unknown, invalid, or expired refresh token","error":"invalid_grant"}

The refresh token cannot be expired as it lasts 24 hours.

My code that initialises Tink resembles this:-

private fun manageTink() {
    try {
        AeadConfig.register()
        authenticatedEncryption = generateNewKeysetHandle().getPrimitive(Aead::class.java)
    } catch (e: GeneralSecurityException) {
        throw RuntimeException(e)
    } catch (e: IOException) {
        throw RuntimeException(e)
    }
}

@Throws(IOException::class, GeneralSecurityException::class)
private fun generateNewKeysetHandle(): KeysetHandle =
    AndroidKeysetManager
        .Builder()
        .withSharedPref(this, TINK_KEYSET_NAME, PREF_FILE_NAME)
        .withKeyTemplate(KeyTemplates.get("AES256_GCM"))
        .withMasterKeyUri(MASTER_KEY_URI)
        .build()
        .keysetHandle

Here is my code for encryption/decryption

import android.util.Base64
import com.google.crypto.tink.Aead
import javax.inject.Inject

const val BASE64_ENCODE_SETTINGS = Base64.NO_WRAP or Base64.NO_PADDING

data class Security @Inject constructor(private val authenticatedEncryption: Aead) {

    fun conceal(plainText: String, associatedData: String): String {
        val plain64 = Base64.encode(plainText.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
        val associated64 = Base64.encode(associatedData.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
        val encrypted: ByteArray? = authenticatedEncryption.encrypt(plain64, associated64)

        return Base64.encodeToString(encrypted, BASE64_ENCODE_SETTINGS)
    }

    fun reveal(encrypted64: String, associatedData: String): String {
        val encrypted = Base64.decode(encrypted64.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
        val associated64 = Base64.encode(associatedData.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
        val decrypted: ByteArray? = authenticatedEncryption.decrypt(encrypted, associated64)

        return String(Base64.decode(decrypted, BASE64_ENCODE_SETTINGS))
    }
}

Could the use of Base64 encode/decode be the issue? Where is my mistake?

1 Answers

If Tink can decrypt your token, the issue should be with your token/the encoding and not with Tink (It is using authenticated encryption, so you are guaranteed that the bytes that you encrypted are going to be the bytes that you decrypt, or you get an error).

To also address the concern in the comment: Tink will happily encrypt and decrypt any byte strings (with the associated data also being allowed to be any byte string), and not be concerned with whether they are printable or even valid Unicode. You will have to base64 encode the output of Tink if you want to use it as a string, though, as it will be uniformly random distributed bytes, i.e. contain invalid Unicode and non printable characters with high probability.

Related