Как я могу генерировать CMAC-AES в JavaScript

Я пытаюсь использовать криптографическую библиотеку Stanford Javascript для создания токена CMAC-AES для утверждения OAuth 2.0, но я далеко не эксперт в области криптографии. Может кто-нибудь привести пример с использованием sjcl или какой-либо библиотеки js с открытой лицензией? Я даже не уверен, что это возможно, используя существующие функции sjcl.

Я попытался использовать объект параметров, как я видел в этом вопросе, но я не понимаю режимы или другие параметры, и я не смог найти никакой документации по этому вопросу. Я полагаю, что соль и iv (чтобы быть воспроизводимым MAC) должны были быть статическими, но я не знаю, какими значениями они должны быть.

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

Немного предыстории... это для переписывания нативных мобильных приложений (iOS и Android) в одно гибридное приложение (Cordova, HTML, CSS и JavaScript), и я хотел бы сохранить как можно больше общего кода в JavaScript, Это не будет работать в "небезопасной" среде браузера, а скорее в нативных WebViews. Если вы знаете о других проблемах безопасности, пожалуйста, я бы хотел их услышать.

В противном случае, я думаю, мне придется написать плагин для передачи CMAC из нативных реализаций.

Забыл упомянуть, я также использую Dojo, и я знаю о dojox.encoding.crypto, но проблема для меня та же. Не уверен, что он может сделать CMAC или сколько изменений потребуется, чтобы заставить его работать.

2 ответа

Решение

Потратил слишком много времени на этих выходных, придумывая это решение, но я наконец-то получил его. Удивительно, сколько мест может пойти не так. Это модуль AMD dojo, поэтому его можно легко загрузить с помощью "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              &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!");
}

Вы могли бы взглянуть на это и реализовать это самостоятельно. Внедрение CMAC должно быть выполнимым, если у вас работает шифрование примитивного блока AES. Самая сложная часть, вероятно, состоит в том, чтобы убедиться, что вы используете реальные байты вместо любого другого типа в JavaScript.

AES CMAC одобрен NIST, поэтому вы можете проводить тестирование на соответствие критериям тестирования.

Другие вопросы по тегам