这个周末花了太多时间想出这个解决方案,但我终于让它工作了。令人惊讶的是有多少地方会出错。这是一个 dojo AMD 模块,因此可以使用“require”轻松加载它。使用由随后的测试向量演示(忽略正常使用中的子键测试)。一个怪癖:确保您将密钥和消息作为十六进制编码字符串传递。我打算让它更友好一点,并接受 utf8String 编码的字符串,但是使用 sjcl.codec 很容易自己完成。
我很感激任何反馈,特别是对于填充函数和我对 bitArray 方法的使用。
// in crypto/AesCmac.js
define([
"dojo/_base/declare"
], function(declare) {
return declare(null, {
/**
* This mostly follows the AES-128 CMAC algorithm found here.
*
* @see http://tools.ietf.org/html/rfc4493
*
*
* This module has a dependency on The Stanford Javascript Crypto Library. If using the
* minified build, be sure to add sjcl.mode.cbc object into the namespace since I guess it's
* too "dangerous" to include.
*
* @see http://crypto.stanford.edu/sjcl/
*
*
* In JavaScript, all numbers are 64 bit floating point. The bitwise operators treat numbers
* as 32bit integers. But we're not guaranteed all 32 bits. ~0=-1 instead of 4,294,967,295.
* So for most of these bit operations we're using sjcl.bitArray to do the dirty work.
*
* @see http://www.hunlock.com/blogs/The_Complete_Javascript_Number_Reference
*
*
* The padding function is described here.
* @see http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_5_basic_organizations.aspx#chap5_6_3_1
*/
const_Bsize : 128, // in bits! not octets (16)
const_Zero : sjcl.codec.hex.toBits("0x00000000000000000000000000000000"),
const_Rb : sjcl.codec.hex.toBits("0x00000000000000000000000000000087"),
aesCipher : {},
init : function(key) {
var keyBits = sjcl.codec.hex.toBits(key);
this.aesCipher = new sjcl.cipher.aes(keyBits);
},
xor4Words : function(x, y) {
return [
x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]
];
},
simpleShiftLeft : function(a, shift) {
return sjcl.bitArray.bitSlice(sjcl.bitArray.concat(a, [
0
]), shift, this.const_Bsize + shift);
},
iso7816d4Padding : function(m) {
var bitLength = sjcl.bitArray.bitLength(m);
m = this.xor4Words(m, this.const_Zero);
var gap = this.const_Bsize - bitLength;
if (gap < 8)
return m;
var startWord = Math.floor(bitLength / 32);
var startByte = Math.ceil((bitLength % 32) / 8); // 0,1,2,3,4
if (startByte == 4) {
console.log("rolled over into next word");
startWord++;
startByte = 0;
if (startWord == 4) {
// this should have been caught above on gap check
console.warn("this shouldn't ever happen");
return m;
}
}
var last32 = m[startWord];
// startByte: 0->2^31, 1->2^23, 2->2^15, 3->2^7
var bitmask = Math.pow(2, (4 - startByte) * 8 - 1)
last32 |= bitmask;
m[startWord] = last32;
return m;
},
_encrypt : function(m) {
return sjcl.bitArray.clamp(sjcl.mode.cbc.encrypt(this.aesCipher, m, this.const_Zero),
this.const_Bsize);
},
generateSubkeys : function() {
// Step 1
var L = this._encrypt(this.const_Zero);
// Step 2
var msbNeg = L[0] & 0x80000000;
var K1 = this.simpleShiftLeft(L, 1, 0);
if (msbNeg) {
K1 = this.xor4Words(K1, this.const_Rb);
}
// Step 3
msbNeg = K1[0] & 0x80000000;
var K2 = this.simpleShiftLeft(K1, 1, 0);
if (msbNeg) {
K2 = this.xor4Words(K2, this.const_Rb);
}
// Step 4
return {
"K1" : K1,
"K2" : K2
};
},
generateCmac : function(plainText) {
// Step 1
var subkeys = this.generateSubkeys();
// Step 2
var M = sjcl.codec.hex.toBits(plainText);
var len = sjcl.bitArray.bitLength(M); // in bits! not octets
var n = Math.ceil(len / this.const_Bsize);
// Step 3
var lastBlockComplete;
if (n == 0) {
n = 1;
lastBlockComplete = false;
} else {
if (len % this.const_Bsize == 0)
lastBlockComplete = true;
else
lastBlockComplete = false;
}
// Step 4
var lastStart = (n - 1) * this.const_Bsize;
var M_last = sjcl.bitArray.bitSlice(M, lastStart);
if (lastBlockComplete) {
M_last = this.xor4Words(M_last, subkeys["K1"]);
} else {
M_last = this.iso7816d4Padding(M_last);
M_last = this.xor4Words(M_last, subkeys["K2"]);
}
// Step 5
var X = this.const_Zero;
var Y;
// Step 6
for ( var i = 1; i <= n - 1; i++) {
var start = (i - 1) * this.const_Bsize;
var end = i * this.const_Bsize;
var M_i = sjcl.bitArray.bitSlice(M, start, end);
Y = this.xor4Words(X, M_i);
X = this._encrypt(Y);
}
Y = this.xor4Words(M_last, X);
// Step 7
return this._encrypt(Y);
}
});
});
这是测试向量
function testAesCmac() {
/**
* <pre>
* Subkey Generation
* K 2b7e1516 28aed2a6 abf71588 09cf4f3c
* AES-128(key,0) 7df76b0c 1ab899b3 3e42f047 b91b546f
* K1 fbeed618 35713366 7c85e08f 7236a8de
* K2 f7ddac30 6ae266cc f90bc11e e46d513b
* </pre>
*/
var AesCmac = require("crypto/AesCmac");
var cmac = new AesCmac();
cmac.init("0x2b7e151628aed2a6abf7158809cf4f3c");
// Test AES-128 on zero initialization vector
var t_0 = cmac._encrypt(cmac.const_Zero);
var aes_0 = sjcl.codec.hex.toBits("0x7df76b0c1ab899b33e42f047b91b546f");
sjcl.bitArray.equal(t_0, aes_0) ? console.log("AES test passed!") : console
.error("AES test failed!");
// Test subkey equality
var subkeys = cmac.generateSubkeys();
var K1 = sjcl.codec.hex.toBits("0xfbeed618357133667c85e08f7236a8de");
sjcl.bitArray.equal(subkeys["K1"], K1) ? console.log("K1 test passed!") : console
.error("K1 test failed!");
var K2 = sjcl.codec.hex.toBits("0xf7ddac306ae266ccf90bc11ee46d513b");
sjcl.bitArray.equal(subkeys["K2"], K2) ? console.log("K2 test passed!") : console
.error("K2 test failed!");
/**
* <pre>
* Example 1: len = 0
* M <empty string>
* AES-CMAC bb1d6929 e9593728 7fa37d12 9b756746
* </pre>
*/
var m1 = "";
var cmac1 = cmac.generateCmac(m1);
var ex1 = sjcl.codec.hex.toBits("0xbb1d6929e95937287fa37d129b756746")
sjcl.bitArray.equal(ex1, cmac1) ? console.log("cmac1 test passed!") : console
.error("cmac1 test failed!");
if (sjcl.codec.hex.fromBits(cmac1) !== "bb1d6929e95937287fa37d129b756746")
console.error(sjcl.codec.hex.fromBits(cmac1) + " !== bb1d6929e95937287fa37d129b756746");
/**
* <pre>
* Example 2: len = 16
* M 6bc1bee2 2e409f96 e93d7e11 7393172a
* AES-CMAC 070a16b4 6b4d4144 f79bdd9d d04a287c
* </pre>
*/
var m2 = "0x6bc1bee22e409f96e93d7e117393172a";
var cmac2 = cmac.generateCmac(m2);
var ex2 = sjcl.codec.hex.toBits("0x070a16b46b4d4144f79bdd9dd04a287c")
sjcl.bitArray.equal(ex2, cmac2) ? console.log("cmac2 test passed!") : console
.error("cmac2 test failed!");
/**
* <pre>
* Example 3: len = 40
* M 6bc1bee2 2e409f96 e93d7e11 7393172a
* ae2d8a57 1e03ac9c 9eb76fac 45af8e51
* 30c81c46 a35ce411
* AES-CMAC dfa66747 de9ae630 30ca3261 1497c827
* </pre>
*/
var m3 = "0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411";
var cmac3 = cmac.generateCmac(m3);
var ex3 = sjcl.codec.hex.toBits("0xdfa66747de9ae63030ca32611497c827")
sjcl.bitArray.equal(ex3, cmac3) ? console.log("cmac3 test passed!") : console
.error("cmac3 test failed!");
/**
* <pre>
* Example 4: len = 64
* M 6bc1bee2 2e409f96 e93d7e11 7393172a
* ae2d8a57 1e03ac9c 9eb76fac 45af8e51
* 30c81c46 a35ce411 e5fbc119 1a0a52ef
* f69f2445 df4f9b17 ad2b417b e66c3710
* AES-CMAC 51f0bebf 7e3b9d92 fc497417 79363cfe
* </pre>
*/
var m4 = "0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
var cmac4 = cmac.generateCmac(m4);
var ex4 = sjcl.codec.hex.toBits("0x51f0bebf7e3b9d92fc49741779363cfe")
sjcl.bitArray.equal(ex4, cmac4) ? console.log("cmac4 test passed!") : console
.error("cmac4 test failed!");
}