encryptioncryptographyaesmontecarlocbc-mode

AES CBC decryption validation for Monte Carlo Tests


I'm trying to perform MCT on AES CBC. The pseudocode is documented here: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/aes/AESAVS.pdf section 6.4.2

Note that there is a mistake in the pseudocode, which is revealed here: What is missing from the AES Validation Standard Pseudocode for the Monte Carlo Tests?

By working in combination with these two references, I am able to come up with a working encryption test for the MCT. The following code shows the working encrypt module (given an initial key, iv, and plaintext as hex strings):

def monte_carlo_encrypt(key_len, initial_key, initial_iv, initial_pt):

    key_array = []
    iv_array = []
    pt_array = []
    ct_array = []

    key_array.append(bytes.fromhex(initial_key))
    iv_array.append(bytes.fromhex(initial_iv))
    pt_array.append(bytes.fromhex(initial_pt))

    for j in range(0, 1000):
        if j == 0:
            ct = encrypt(key_array[0], iv_array[0], pt_array[j])
            ct_array.append(ct)
            pt_array.append(iv_array[0])
        else:
            ct = encrypt(key_array[0], ct_array[j - 1], pt_array[j])
            ct_array.append(ct)
            pt_array.append(ct_array[j - 1])

    if key_len == 128:
        key_array.append(bytes(a ^ b for a, b in zip(key_array[0], ct_array[j])))
    elif key_len == 256:
        key_array.append(bytes(a ^ b for a, b in zip(key_array[0], ct_array[j - 1] + ct_array[j])))

    iv_array.append(ct_array[-1])
    pt_array.clear()
    pt_array.append(ct_array[-2])

    return key_array[-1], iv_array[-1], pt_array[-1], ct_array[-1]
                    
for k in range(0, 100):
    (next_key, next_iv, next_pt, next_ct) = monte_carlo_encrypt(key_len * 8,
                                                                key.hex().upper(),
                                                                iv.hex().upper(),
                                                                given_pt.hex().upper())

     key = next_key
     iv = next_iv
     given_pt = next_pt

The above test produces the correct results, which can be tested with the standards outlined in the stackoverflow question linked above.

Now the issue comes up when I try to adapt this for decryption. According to the NIST reference, "The pseudocode for
decryption can be obtained by replacing all PT’s with CT’s and all CT’s with PT’s. "

So, I tried that, and came up with this:

def monte_carlo_decrypt(key_len, initial_key, initial_iv, initial_ct):

    key_array = []
    iv_array = []
    pt_array = []
    ct_array = []

    key_array.append(bytes.fromhex(initial_key))
    iv_array.append(bytes.fromhex(initial_iv))
    ct_array.append(bytes.fromhex(initial_ct))

    for j in range(0, 1000):
        if j == 0:
            pt = decrypt(key_array[0], iv_array[0], ct_array[j])
            pt_array.append(pt)
            ct_array.append(iv_array[0])
        else:
            pt = decrypt(key_array[0], pt_array[j - 1], ct_array[j])
            pt_array.append(pt)
            ct_array.append(pt_array[j - 1])

    if key_len == 128:
        key_array.append(bytes(a ^ b for a, b in zip(key_array[0], pt_array[j])))
    elif key_len == 256:
        key_array.append(bytes(a ^ b for a, b in zip(key_array[0], pt_array[j - 1] + pt_array[j])))

    iv_array.append(pt_array[-1])
    ct_array.clear()
    ct_array.append(pt_array[-2])

    return key_array[-1], iv_array[-1], pt_array[-1], ct_array[-1]

for k in range(0, 100):
    (next_key, next_iv, next_pt, next_ct) = monte_carlo_decrypt(key_len * 8,
                                                                key.hex().upper(),
                                                                iv.hex().upper(),
                                                                given_ct.hex().upper())
     key = next_key
     iv = next_iv
     given_ct = next_ct

But this decryption module gets the decryption wrong on the very first iteration. Given:

        "iv": "9982F2D532BC341791ECC30A1FEA9A3F",
        "key": "A58C28340553879F488E161CF815D104",
        "ct": "349F129B75B99E845D99090B26801D12"

It should produce:

            "key": "A58C28340553879F488E161CF815D104",
            "iv": "9982F2D532BC341791ECC30A1FEA9A3F",
            "pt": "9B2F1D63DE3809F47E40EFB885F01277",
            "ct": "349F129B75B99E845D99090B26801D12"

But mine produces:

                            "key": "A58C28340553879F488E161CF815D104",
                            "iv": "9982F2D532BC341791ECC30A1FEA9A3F",
                            "pt": "23119C0130042FDA973D171E9E9E4921",
                            "ct": "349F129B75B99E845D99090B26801D12"

Does anyone see where I went wrong with my decryption implementation?


Solution

  • I figured out the answer. I found this reference, which was much more helpful in terms of pseudocode than the NIST reference: https://www.ipa.go.jp/security/jcmvp/jcmvp_e/documents/atr/atr01b_en.pdf section 3.4.3.2.2

    Here is the working decrypt code:

    def monte_carlo_decrypt(key_len, initial_key, initial_iv, initial_ct):
    
        key_array = []
        iv_array = []
        pt_array = []
        ct_array = []
    
        key_array.append(bytes.fromhex(initial_key))
        iv_array.append(bytes.fromhex(initial_iv))
        ct_array.append(bytes.fromhex(initial_ct))
    
        for j in range(0, 1000):
            pt = decrypt(key_array[0], iv_array[j], ct_array[j])
            pt_array.append(pt)
            if j == 0:
                tmp = ct_array[j]
                ct_array.append(iv_array[j])
                iv_array.append(tmp)
            else:
                iv_array.append(ct_array[j])
                ct_array.append(pt_array[j - 1])
    
        if key_len == 128:
            key_array.append(bytes(a ^ b for a, b in zip(key_array[0], pt_array[j])))
        elif key_len == 256:
            key_array.append(bytes(a ^ b for a, b in zip(key_array[0], pt_array[j - 1] + pt_array[j])))
    
        iv_array.append(pt_array[-1])
        ct_array.clear()
        ct_array.append(pt_array[-2])
    
        return key_array[-1], iv_array[-1], pt_array[-1], ct_array[-1]
    
    for k in range(0, 100):
        (next_key, next_iv, next_pt, next_ct) = monte_carlo_encrypt(key_len * 8,
                                                                key.hex().upper(),
                                                                iv.hex().upper(),
                                                           given_pt.hex().upper())
        key = next_key
        iv = next_iv
        given_pt = next_pt