Search code examples
c++encryptionaescrypto++atmel

AtmelATAES132 Mac generation


I'm trying to authenticate against an Atmel ATAES132 Crypto Chip from a C++ application. To compute the MAC I want to use the Crypto++ library. The chip uses AES CCM to computes a MAC that is compared against a MAC I must compute in software.

MAC generation is described in the datasheet as crypting a 128bit datablock (B0) with CBC mode, XORing it with 128bit Authentication Only data and XORing the result with another 128bit datablock (A0) encrypted with AES CTR mode.

What I'm doing is this:

string macB0string;

macB0string.append("\x79", 1);
macB0string.append(nonceString);
macB0string.append("\x01\x00\x00", 3);

// Authentication Only Data
byte aa[] = {
    0x00, 0x0E, 0x00, 0xEE, 0x03, 0x01, 0x00, 0x01, 0x00, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};

    string input, output;

input.append(macB0string);

byte zeroIV[16];
memset(zeroIV, 0, sizeof(zeroIV));

CBC_Mode< AES >::Encryption cbc;
cbc.SetKeyWithIV(key, sizeof(key), zeroIV);


StringSource(input, true,
    new StreamTransformationFilter(cbc,
        new StringSink(output),
        StreamTransformationFilter::NO_PADDING
        )
);

// Encrypt aa with same key and previous computed cipher as IV
// This is our cleartext MAC
CBC_Mode< AES >::Encryption cbc2;
cbc2.SetKeyWithIV(key, sizeof(key), (byte*)output.c_str());

string output2;
StringSource(string((const char*)aa, sizeof(aa)), true,
    new StreamTransformationFilter(cbc2,
        new StringSink(output2),
        StreamTransformationFilter::NO_PADDING
        )
);

string macA0;

macA0.append("\0x01", 1);
macA0.append(nonceString);
macA0.append("\0x01\0x00\x00", 3);


// Encrypt cleartext MAC
CTR_Mode< AES >::Encryption ctr;
ctr.SetKeyWithIV(key, sizeof(key), (byte*)macA0.c_str());

string MAC;

StringSource(output2, true, 
    new StreamTransformationFilter(ctr,
        new StringSink(MAC)
    )
);

This is exactly what is explained in the datasheet on page 112. But an authentiation attempt with the gerated MAC fails. I used a zero filled IV in the first CBC encryption because I understood that CCM is like CBC-MAC with zero IV plus CTR encryption.

If someone with experience with the ATAES132 could point me in the right direction what is going wrong I would be very gratefull.

EDIT

This is how the CCM process is described in the datasheet.

The following example shows how the integrity MAC is calculated for an authentication operation requiring up to 14 bytes of authenticate-only data. This operation involves three passes through the AES crypto engine; all three using the same key. If there are more than 14 bytes of authenticate-only data, then another pass through the AES crypto engine is required.

There are two passes through the AES crypto engine in CBC mode to create the cleartext MAC. The inputs to the crypto engine for those blocks are labeled B0 and B1, and the outputs are B’0 and B’1, respectively.

  • B0 is composed of the following 128 bits:
    • 1 byte flag, a fixed value of b0111 1001.
    • 12 byte Nonce, as generated by the Nonce command.
    • 1 byte MacCount, 1 for first MAC generation.
    • 2 byte length field, always 0x00 00 for authentication only.
  • B1 is the XOR of B’0 with the following 128 bits:
    • 2 byte length field, size of authenticate-only data.
    • 14 byte data to be authenticated only.
  • B’1 is the cleartext MAC, which must be encrypted before being sent to the system.

There is one additional pass through the AES crypto engine in CTR mode to create the key block that is used to encrypt the MAC. The input to the crypto engine for this block is labeled A0 and the output is A’0. A’0 is the MAC sent to the system as the output parameter of the Auth command.

  • A0 is composed of the following 128 bits:
    • 1 byte flag – fixed value of b0000 0001.
    • 12 byte Nonce – as generated by ATAES132 during Nonce command.
    • 1 byte MacCount – one for first MAC generation.
    • 2 byte counter field – always 0x00 00 for A0.
  • A’0 is XOR’d with the cleartext MAC (B’1) and sent to the system.

And this is the code I tried

byte key[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
    0x0C, 0x0D, 0x0E, 0x0F
}; // Testkey Set in Chip as KeyID 1

byte nonce[] = {
    0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11
};

byte crc[2];

string nonceCommand;

nonceCommand.append("\x01", 1); // Opcode
nonceCommand.append("\x00", 1); // Mode: 00 = Set Nonce
nonceCommand.append("\x00\x00\x00\x00", 4); // 4 Bytes Zero
nonceCommand.append( string((const char*)nonce, sizeof(nonce)) );

char count = nonceCommand.length() + 3; // length + 1byte count and 2bytes
                                        //  crc

nonceCommand.insert(0, &count, 1);
CalcCRC(nonceCommand.length(), (byte*)nonceCommand.c_str(), crc);
nonceCommand.append( string((const char*)crc, sizeof(crc)) );


// SNIP
// Send SetNonceCommand and read response
// SNIP

string B0;
B0.append("\x79", 1); // FixedValue
B0.append( string((const char*)nonce, sizeof(nonce)) );
B0.append("\x01", 1); // MAC Count, 1 for first generation
B0.append("\x00\x00", 2); // 2 byte length field

string AA;
AA.append("\x00\xEE", 2); // Manufacturing ID
AA.append("\x03", 1); // Opcode
AA.append("\x02", 1); // Outbound authentication (we receive a MAC)
AA.append("\x00\x01", 2); // Key ID
AA.append("\x00\x07", 2); // Usage
AA.append("\x01", 1); // Mac Flag (1 for first generation)
AA.append("\x00\x00\x00\x00", 4); // 4 Bytes Zero
AA.append("\x00", 1); // 1 Byte padding

count = AA.length();
AA.insert(0, &count, 1);
AA.insert(0, "\x00", 1);

// CCM 
const int TAG_SIZE = 16;
CCM< AES, TAG_SIZE >::Encryption ccm;
ccm.SetKeyWithIV(key, sizeof(key), nonce, sizeof(nonce));
ccm.SpecifyDataLengths(AA.length(), B0.length(), 0);

string cipher;
AuthenticatedEncryptionFilter ef(ccm,
    new StringSink(cipher)
);

ef.ChannelPut(AAD_CHANNEL, (byte*)AA.c_str(), AA.length());
ef.ChannelMessageEnd(AAD_CHANNEL);

ef.ChannelPut(DEFAULT_CHANNEL, (byte*)B0.c_str(), B0.length());
ef.ChannelMessageEnd(DEFAULT_CHANNEL);

string enc = cipher.substr(0, cipher.length() - TAG_SIZE);
string tag = cipher.substr(cipher.length() - TAG_SIZE);

// Get Outbound MAC from IC
string authCommand;
authCommand.append("\x03", 1); // Opcode
authCommand.append("\x02", 1); // Mode Outbound only
authCommand.append("\x00\x01", 2); // KeyID
authCommand.append("\x00\x07", 2); // Usage

count = authCommand.length() + 3;
authCommand.insert(0, &count, 1);
CalcCRC(authCommand.length(), (byte*)authCommand.c_str(), crc);
authCommand.append( string((const char*)crc, sizeof(crc)) );

// SNIP
// Send Outbound Authentication Command to IC and receive response
// SNIP

This results in

enc:    96 01 a1 0d ef 1e 5f f6 5f 9d 91 7e 80 25 71 a4 
tag:    71 2b a3 6a 7c 35 49 63 46 4c 58 0e a9 4a 2c 5e 

But the IC send an authentication MAC of

ea c1 fd 60 9f 93 89 87 63 8f 9a df ee 17 85 bb

I don't quite get what the right input parameters for CCM mode are given the description from the datasheet


Solution

  • I found out how to use Crypto++ AES CCM to compute the MAC for the ATAES132. What is described in the datasheet is AES CCM but already computed for the CCM algorithm. You can't just put it into the CCM function.

    If you want to use Crypto++ CCM Mode to compute a MAC do as follows:

    // Compute a 12 Byte Nonce as described in the datasheet
    // Get the ManufacturingID from the IC
    
    string iv = string((const char*)nonce, sizeof(nonce));
    iv.append( MACCount ); // MAC Count is part of the IV giving it a size of 13
    
    string authOnly, empty, cipher;
    
    authOnly.append("\x00\xEE", 2); // Manufacturing ID
    authOnly.append("\x03", 1); // Opcode
    authOnly.append("\x02", 1); // Outbound authentication (we receive a MAC)
    authOnly.append("\x00\x01", 2); // Key ID
    authOnly.append("\x00\x07", 2); // Usage
    authOnly.append("\x01", 1); // Mac Flag !!!NOT MACCOUNT!!!
    authOnly.append("\x00\x00\x00\x00", 4); // 4 Bytes Zero
    authOnly.append("\x00", 1); // 1 Byte padding
    
    CCM< AES, 16 >::Enryption ccm;
    ccm.SetKeyWithIV(key, sizeof(key), (byte*)iv.c_str(), iv.length());
    
    ccm.SpecifyDataLengths(authOnly.length(), empty.length(), 0); // empty is an empty string
    AuthenticatedEncryptionFilter ef(ccm,
        new StringSink(cipher)
    );
    
    ef.ChannelPut(AAD_CHANNEL, (byte*)authOnly.c_str(), authOnly.length());
    ef.ChannelMessageEnd(AAD_CHANNEL);
    
    ef.ChannelPut(DEFAULT_CHANNEL, (byte*)empty.c_str(), empty.length());
    ef.ChannelMessageEnd(DEFAULT_CHANNEL);
    
    string tag = cipher.substr( cipher.length() - TAG_SIZE);
    

    Now you should have the same 16 Byte MAC as the IC has computed.