2

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.

Wasae Shoaib
  • 189
  • 3
  • 19
  • 2
    This means that they have used a MAC, likely HMAC over the plaintext to calculate the IV. However, without details about key usage, hash algorithm, which bytes of the output are used etc. it will be pretty tricky to find out how to calculate the IV. More documentation is required unless you want to take a lot of time testing. – Maarten Bodewes May 19 '22 at 15:07

0 Answers0