I have a usecase where I would want to keep a backup of the encryption keys in case the vault server goes down and decrypt it locally. I am using Hashicorp Transit Secrets Engine, and to test it out I am using Vault Test Container with Spring Vault. I have managed to locally decrypt non-convergent keys but have not been able to figure out how to do that for convergent keys. I am not a crypto expert so I might be missing some basic understanding.
Below is the code that is working for non-convergent keys
@Test
fun backUpAndDecrypt(){
val dataKeyName = "test-key"
val dataToEncrypt = "Customer Password"
val vTemp = VaultTemplate(
VaultEndpoint.from(URI.create("http://" + vaultContainer?.host + ":" + vaultContainer?.firstMappedPort)),
TokenAuthentication("my-root-token")
)
val transitOperations: VaultTransitOperations = vTemp.opsForTransit()
transitOperations.createKey(
dataKeyName,
VaultTransitKeyCreationRequest.builder().exportable(true).build()
)
val ciphertext = transitOperations.encrypt(
dataKeyName,
dataToEncrypt
)
val keyBackups = transitOperations.exportKey(dataKeyName, TransitKeyType.ENCRYPTION_KEY)
assertThat(keyBackups.keys.get("1")?.let { decrypt(ciphertext.substring(9), it) }).isEqualTo(dataToEncrypt)
}
@Throws(GeneralSecurityException::class)
fun decrypt(cyphertext: String?, encodedKey: String): String? {
val GCM_TAG_SIZE_BITS = 128
val GCM_IV_SIZE_BYTES = 12
val decodedKey: ByteArray = Base64.getDecoder().decode(encodedKey)
val originalKey: SecretKey = SecretKeySpec(decodedKey, 0, decodedKey.size, "AES")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val ivAndCTWithTag = Base64.getDecoder().decode(cyphertext)
val spec = GCMParameterSpec(GCM_TAG_SIZE_BITS, ivAndCTWithTag, 0, GCM_IV_SIZE_BYTES)
cipher.init(Cipher.DECRYPT_MODE, originalKey, spec)
val plaintext = cipher.doFinal(ivAndCTWithTag, GCM_IV_SIZE_BYTES, ivAndCTWithTag.size - GCM_IV_SIZE_BYTES)
return String(plaintext, Charset.defaultCharset())
}
Now there is a scenario in which I need to pass a context and generate a convergent ciphertext. The question is how to decrypt it locally. Below is the stub which is to be populated
@Test
fun backUpAndDecryptConvergent(){
val convergentDataKeyName = "test-convergent-key"
val dataToEncrypt = "Customer Password"
val contextOne = "{\"service\": \"customers\", \"id\": \"some-customer-uuid\"}".toByteArray()
val vTemp = VaultTemplate(
VaultEndpoint.from(URI.create("http://" + vaultContainer?.host + ":" + vaultContainer?.firstMappedPort)),
TokenAuthentication("my-root-token")
)
val transitOperations: VaultTransitOperations = vTemp.opsForTransit()
transitOperations.createKey(
convergentDataKeyName,
VaultTransitKeyCreationRequest.builder().convergentEncryption(true).derived(true).exportable(true).build()
)
val convergentCiphertext = transitOperations.encrypt(
convergentDataKeyName,
dataToEncrypt.toByteArray(),
VaultTransitContext.fromContext(contextOne)
)
val keyBackups = transitOperations.exportKey(convergentDataKeyName, TransitKeyType.ENCRYPTION_KEY)
assertThat(keyBackups.keys.get("1")?.let { decrypt(convergentCiphertext.substring(9), it) }).isEqualTo(dataToEncrypt)
}
The only thing available on the official page is that
Version 3 uses a different algorithm designed to be resistant to offline plaintext-confirmation attacks. It is similar to AES-SIV in that it uses a PRF to generate the nonce from the plaintext.