AES CMAC
nb may need to chain the IV
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.NoSuchAlgorithmException;
import javax.crypto.spec.SecretKeySpec;
/**
*
* @author Chris Skinner
*/
public class AES {
static public byte[] K = new byte [16]; // 128 bit key
static public byte[] K1 = new byte [16]; // 128 bit sub key
static public byte[] K2 = new byte [16]; // 128 bit sub key
static final public byte[] Z16 = new byte [16]; // 128 bit zero
static Cipher cipher = null;
//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
static void print (String s) {
System.out.print(s);
}
//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
static byte[] shl (byte[] bin) // << 16 byte array
{
byte[] bout = new byte[16];
for (short j = 0; j < 15; j++) // java b[0] is the highorder
{
int sot = ((bin[j+1] & 0x80) >> 7);
int sef = ( bin[j] << 1 ) | sot;
bout[j] = ( byte)sef;
}
bout[15] = (byte)(bin[15] << 1);
return bout;
}
//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
static SecretKeySpec skeySpec = null;
public static void subKeys(byte[] key) // make K1 K2 from key
{
/*
+ Algorithm Generate_Subkey +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ +
+ Input : K (128-bit key) +
+ Output : K1 (128-bit first subkey) +
+ K2 (128-bit second subkey) +
+-------------------------------------------------------------------+
+ +
+ Constants: const_Zero is 0x00000000000000000000000000000000 +
+ const_Rb is 0x00000000000000000000000000000087
binary 10000111 +
+ Variables: L for output of AES-128 applied to 0^128 +
+ +
+ Step 1. L := AES-128(K, const_Zero); +
+ Step 2. if MSB(L) is equal to 0 +
+ then K1 := L << 1; +
+ else K1 := (L << 1) XOR const_Rb; +
+ Step 3. if MSB(K1) is equal to 0 +
+ then K2 := K1 << 1; +
+ else K2 := (K1 << 1) XOR const_Rb; +
+ Step 4. return K1, K2; +
+ +
*/
byte bRb = (byte)0x87; // Rb for AES128
// key must be 16 bytes
try
{
skeySpec = new SecretKeySpec(key, "AES");
if (cipher == null)
cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
K1 = cipher.doFinal(Z16);
}
catch (Exception ex) // nosuchalgorithm, invalidkey,nosuchpadding...
{
print("\n error 400 AES " + ex.getMessage());
}
boolean highL = ((K1[0] & 0x80) != 0);
K1 = shl(K1);
if (highL)
K1[15] = (byte)(K1[15] ^ bRb);
highL = ((K1[0] & 0x80) != 0);
K2 = shl(K1);
if (highL)
K2[15] = (byte)(K2[15] ^ bRb);
}
//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
static byte[] xor16(byte[] ba, byte[] bb)
{
byte[] bout = new byte[ba.length];
for (short j = 0; j < ba.length; j++)
bout[j] = (byte)(ba[j] ^ bb[j]);
return bout;
}
//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
static public byte[] CMAC(byte[] key, byte[] mesg)
{
/*
+ Input : K ( 128-bit key ) +
+ : M ( message to be authenticated ) +
+ : len ( length of the message in octets ) +
+ Output : T ( message authentication code ) +
+
+ const_Bsize is 16 +
+ +
+ Variables: K1, K2 for 128-bit subkeys +
+ M_i is the i-th block (i=1..ceil(len/const_Bsize)) +
+ M_last is the last block xor-ed with K1 or K2 +
+ n for number of blocks to be processed +
+ r for number of octets of last block +
+ flag for denoting if last block is complete or not +
+ +
+ Step 1. (K1,K2) := Generate_Subkey(K); +
+ Step 2. n := ceil(len/const_Bsize);
The smallest integer no smaller than x.
ceil(3.5) is 4. ceil(5) is 5.+
+ Step 3. if n = 0 +
+ then +
+ n := 1; +
+ flag := false; +
+ else +
+ if len mod const_Bsize is 0 +
+ then flag := true; no overflow +
+ else flag := false; +
+ +
+ Step 4. if flag is true +
+ then M_last := M_n XOR K1; +
+ else M_last := padding(M_n) XOR K2; +
+ Step 5. X := const_Zero; +
+ Step 6. for i := 1 to n-1 do +
+ begin +
+ Y := X XOR M_i; +
+ X := AES-128(K,Y); +
+ end +
+ Y := M_last XOR X; +
+ T := AES-128(K,Y); +
+ Step 7. return T; +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
subKeys(key); // set K1,K2
int mz = mesg.length ;
print("\n\n message " + Uti.asString(mesg)); //✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
int n = mz / 16; // number of 16byte chunks.. if overflow > 0 add 1 to this
print("\n mz " + mz + " first n " + n); //✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
int m = n * 16;
int v = mz - m; // overflow bytes 0..15
print(" m " + m + " v " + v);//✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
if (v > 0)
n++; // now the "actual" number of chunks ie ceiling
print(" new n " + n);//✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
byte[] MLast = new byte[16];
int lastn = ( n-1 ) * 16; //byte address of lastchunk within mesg
int lastz = mz -lastn; // number of bytes to copy to lastchunk
System.arraycopy(mesg,lastn, MLast,0,lastz);
print("\n MLast " + Uti.asString(MLast)); //✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
if (v == 0) // no overflow
MLast = xor16(MLast,K1);
else
{
MLast[lastz] = (byte)0x80; // this does the padding
print("\n v pre MLast " + Uti.asString(MLast)); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
MLast = xor16(MLast,K2);
print("\n v post MLast " + Uti.asString(MLast)); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
}
//todo: put MLast backk into mesg and do a normal CBC....
// we should be able to use CBC... // todo replace with CBC
byte[] X = new byte[16]; // zeros by default;
//BUT updated IV has to be used in next CMAC or encryption
byte[] plain = new byte[16];
try
{
if (cipher == null)
cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec); // spec set in subkeys???
for (short j = 0 ; j < n-1; j++)
{
int jx = j * 16; // dont do this kind of thing...
System.arraycopy(mesg,jx, plain,0,16);
plain = xor16(plain,X);
print("\n plain aaa " + Uti.asString(plain)); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
X = cipher.doFinal(plain);
}
plain = xor16(MLast,X);
print("\n plain zzz " + Uti.asString(plain)); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
X = cipher.doFinal(plain);
print("\n cipherzzz " + Uti.asString(X)); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
}
catch (Exception ex) // nosuchalgorithm, invalidkey,nosuchpadding...
{
print("\n error 400 AES " + ex.getMessage());
}
return X;
}
}//♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧ fin ♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧♧
This CMAC works with DESFire cards ("EV1")
ReplyDelete- Chaining required, ie previous MAC is XORed with X at start.
BUT it does not work with DESFire SAM, when attempting to "Activate" (convert from AV1 to AV2)
- Can it be possible that DESFire use different CMACs for the Card and the SAM?
What a hideous possibility!
I know I can do a "standard" CMAC on a 16byte input
ReplyDeletebyte[] M2 = Uti.asHex("6bc1bee2 2e409f96 93d7e11 7393172a");
T = AES.CMAC(K,M2);
print("\n\n Ex 2 " + Uti.asString(T));
print( "\n expect 070a16b4 6b4d4144 f79bdd9d 04a287c");
So DESFire "Activate" - version of Lock/UnLock seems to use a non-standard. ZUT ALORS!
OR: they are unable to describe accurately the field to be MACed - (Isnt the entire art of computer programming the understanding of inadequate specifications? If specifications were adequate, there would be no programmers.)
Forgot the Key
ReplyDeletethese from NIST
http://csrc.nist.gov/publications/nistpubs/800-38B/Updated_CMAC_Examples.pdf
byte[] K = Uti.asHex("2b7e1516 28aed2a6 abf71588 09cf4f3c");
maybe its a problem with AES(0,0)
ReplyDelete- How I wish that Sun Java Forums was still extant
Oracle took it over and killed it... a great learning resource gone
// This May be how the 16 byte AES CMAc is truncated to 8 bytes
ReplyDelete// .. Not NIST Special publication 800-38B standard...
static public byte[] truncCMAC(byte[] key, byte[] mesg) // 8 byte truncated MAC
{
byte[] cmac = CMAC(key,mesg); // 800-38B standard
byte[] tmac = new byte[8];
// assuming Java big-endian
tmac[0] = cmac[1];
tmac[1] = cmac[3];
tmac[2] = cmac[5];
tmac[3] = cmac[7];
tmac[4] = cmac[9];
tmac[5] = cmac[11];
tmac[6] = cmac[13];
tmac[7] = cmac[15];
return tmac;
}
Hi,
ReplyDeleteI'm looking for the way how to generate a CMAC in Java to use for the communication with an EV1 card. In detail I try to read a fully enciphered communication with AES. I've got a example with example values but did not find a cmac method which generates these values yet.
I'm working with the appnotes from NXT. They use AES as cipher and work with IV.
I also read your blog entry about CRC32 (with invert and read it from end to begin etc.) so maybe it's a problem like that. So that i have the right value but not "right displayed".
I'm from Germany and very sorry for my english. Hope you understand most of my problem.
S.P.
Hi,
ReplyDeletei successfully generated a cmac like in the appnotes from NXP.
I did some extensions:
Cipher AES with IV
and after some problems with padding it works.
Stefan
Hi Stefan,
ReplyDeletecould you put your source code with some "extensions", becouse i can't generate CMAC according to NXP documents.