javascript - 如何在 javascript 中生成 CMAC-AES

标签 javascript oauth dojo cryptography cmac

我正在尝试使用 Stanford Javascript Crypto Library为 OAuth 2.0 断言生成 CMAC-AES token ,但我远不是密码学专家。有人可以举一个使用 sjcl 或任何开放许可 js 库的例子吗?我什至不确定是否可以使用 sjcl 的现有功能。

我尝试使用我在 this question 中看到的选项对象,但我不了解这些模式或其他选项,而且我找不到任何相关文档。我认为 salt 和 iv(要成为可重现的 MAC)必须是静态的,但我不知道它们应该是什么值。

// is there a way that works?
var token = sjcl.encrypt(secret,assertion,{salt:foo,iv:bar});

一些背景知识...这是为了将 native 移动应用程序(iOS 和安卓)重写为单个混合应用程序(Cordova、HTML、CSS 和 JavaScript),我希望尽可能多地共享代码尽可能使用 JavaScript。这不会在“不安全”的浏览器环境中运行,而是在 native WebView 中运行。如果您知道其他安全问题,请告诉我。

否则我想我将不得不编写一个插件来从 native 实现中传递 CMAC。

忘了说,我也在使用 Dojo,我知道 dojox.encoding.crypto 但问题对我来说是一样的。不确定它是否可以执行 CMAC 或需要多少修改才能使其工作。

最佳答案

这个周末花了太多时间想出这个解决方案,但我终于成功了。令人惊奇的是它有多少地方会出错。这是一个 dojo AMD 模块,因此可以使用“require”轻松加载它。用法由后面的测试向量演示(忽略正常使用中的子键测试)。一个怪癖:确保您将 key 和消息作为十六进制编码的字符串传递。我计划使其更友好一些,并也接受 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              &lt;empty string&gt;
 * 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!");
}

关于javascript - 如何在 javascript 中生成 CMAC-AES,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14150250/

相关文章:

javascript - Dojo Tooltip Spring 和变量

javascript - 如何使 dijit/编辑器手动调整大小?

javascript - 删除数据库中的一行 (JSP)

javascript - 在网络软件中启用辅助用户帐户

oauth-2.0 - 谷歌开发者控制台,不同用户的不同同意屏幕设置

oauth - .NET 核心 OpenId 连接服务器 : Sharing same token across multiple applications

javascript - 为什么这个数据网格以奇怪的顺序排序?

javascript - 如何替换 javascript es6 中的 jQuery .val() 函数?

javascript - 如何在 React 中过滤列表为空时显示消息

ruby-on-rails - Rails 2.3.5 中用于 oAuth2 消费者和提供者功能的 Gem