Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct implementation of AES 128 encryption with initialization vector and padding

I implemented AES 128 bit encryption using initialization vector and padding, as seen in the code below. I happen to be using ColdFusion, but I don’t think that matters. The encrypted result shows some repeating patterns, which I would not have expected, but then again I don't know the characteristics of correct output for this. Am I doing initialization vector and padding correctly?

<!---
To encrypt this, for example:
    "String1"
Prefix the string with an Initialization Vector of 16 random characters,
plus enough padding ("000000001") to make the entire string a multiple of 16 characters (32 characters, here)
    "HoMoz4yT0+WAU7CX000000001String1"
Now encrypt the string to this (64 characters):
    "Bn0k3q9aGJt91nWNA0xun6va8t8+OiJVmCqv0RzUzPWFyT4jUMzZ56pG5uFt6bGG"
--->

<cfoutput>

    <cfset EncryptKey="LpEecqQe3OderPakcZeMcw==">

    <cfloop index="StringToEncrypt" list="String1,String2,String3,String3">
        <!--- Make random Initialization Vector (IV) of length 16 
        (create it from GenerateSecretKey(), but GenerateSecretKey is NOT the key that we encrypt/decrypt with) --->
        <cfset IV=left(GenerateSecretKey("AES",128),16)>
        <!--- Pad the string so its length is a multiple of 16 --->
        <cfset padlength=16 - (len(StringToEncrypt) mod 16)>
        <cfset padding=repeatstring("0",padlength-1) & "1">
        <cfset NewStringToEncrypt=IV & padding & StringToEncrypt>
        <cfset EncryptedString=encrypt(NewStringToEncrypt,EncryptKey,"AES","Base64")>
<pre>Original string: #StringToEncrypt#
StringToEncrypt: #NewStringToEncrypt#
EncryptedString: #EncryptedString#</pre>
    </cfloop>

</cfoutput>

Below is sample output:

Original string: String1
StringToEncrypt: QLkApY6XKka7mQge000000001String1
EncryptedString: BOAVeSKidQyyHrEa15x9Uava8t8+OiJVmCqv0RzUzPWFyT4jUMzZ56pG5uFt6bGG

Original string: String2
StringToEncrypt: DboCmHHuVrU05oTV000000001String2
EncryptedString: 4Yk14F0ffz9+djbvSiwA1/X3FHhS5Vhta7Q8iocBPhmFyT4jUMzZ56pG5uFt6bGG

Original string: String3
StringToEncrypt: 8om5VbbWQgvRWK7Q000000001String3
EncryptedString: 01AF+pmF9sDsUHcIXSVfom8Egv8Oiyb2yy12hiVcJjqFyT4jUMzZ56pG5uFt6bGG

Original string: String3
StringToEncrypt: T4qJodVe6aEv0p1E000000001String3
EncryptedString: aAjCbSBRZ+cd7ZwpFPZUxW8Egv8Oiyb2yy12hiVcJjqFyT4jUMzZ56pG5uFt6bGG

Each EncryptedString ends with the same 21 characters:

FyT4jUMzZ56pG5uFt6bGG

When the original string is the same ("String3" in the 3rd and 4th example), the EncryptedString ends with the same 42 characters:

8Egv8Oiyb2yy12hiVcJjqFyT4jUMzZ56pG5uFt6bGG

Update: Per the accepted answer, I should not do my own padding or initialization vector. Coldfusion's encrypt/decrypt functions can handle that automatically, and the encrypted values will have no repeating patterns. For example:

EncryptedString=encrypt(StringToEncrypt, EncryptKey, 'AES/CBC/PKCS5Padding', 'Base64')
DecryptedString=decrypt(EncryptedString, EncryptKey, 'AES/CBC/PKCS5Padding', 'Base64')
like image 503
FlanMan Avatar asked Jul 06 '19 17:07

FlanMan


People also ask

Which is the correct order of steps performed in each round of AES?

To review the overall structure of AES and to focus particularly on the four steps used in each round of AES: (1) byte substitution, (2) shift rows, (3) mix columns, and (4) add round key.

Does AES need an initialization vector?

AES algorithm requires two different parameters for encryption, a key and an initialization vector (IV).

What is the use of initialization vector in AES?

An initialization vector (or IV) are used to ensure that the same value encrypted multiple times, even with the same secret key, will not always result in the same encrypted value. This is an added security layer.


1 Answers

Do not do your own padding, but let the encrypt function do it. It's appended to the plaintext, not prepended. The usual padding is called PKCS5 padding and adds $t$ times the byte $t \in {1,2,3,\ldots,16}$ to make up full blocks.

Also, the iv is an argument to the encrypt function and not prepended before the plaintext. It's actually (when you use a chain-mode like CBC, OFB, CFB) prepended to the ciphertext.

So you could generate the IV as you do (though there are probably better functions to do it) and then use the plaintext as is and encrypt:

EncryptedString=encrypt(StringToEncrypt, EncryptKey, 'AES/CBC/PKCS5Padding', 'Base64', binaryDecode(IV, "base64"))

(based on this documentation)

added 2 The iv should be binary according to these docs, so the base64 from generateKey has to be converted. Thx to comments by @Agax and his "cftry" examples...

added 1 It turns out that omitting the IV will cause the function to generate its own IV, and in a seemingly non-repeating way. In any case we can use

EncryptedString=encrypt(StringToEncrypt, EncryptKey, 'AES/CBC/PKCS5Padding', 'Base64')

instead. It's essential to use a chaining mode like CBC to get the propagation effect from a random IV that masks identical plaintexts and which the default 'AES' leaves visible (this does ECB mode, presumably).

The IV is actually not prepended to the ciphertext, when we give it as an argument, so you have to remember it somehow to be able to decrypt the first block of plain text. Quite non-standard. When you let encrypt generate it itself, it does prepend it. So you have to use the same signature version of decrypt.

like image 167
Henno Brandsma Avatar answered Sep 30 '22 10:09

Henno Brandsma