1. 论坛系统升级为Xenforo,欢迎大家测试!
    排除公告

AES(Rijndael) 算法的 JavaScript 实现(ZT)

本帖由 不学无术2005-12-21 发布。版面名称:前端开发

  1. 不学无术

    不学无术 Ulysses 的元神

    注册:
    2005-08-31
    帖子:
    16,714
    赞:
    39
    文章来源:http://www.coolcode.cn/?p=94

    PHP:
    // Rijndael parameters --  Valid values are 128, 192, or 256

    var keySizeInBits 128;
    var 
    blockSizeInBits 128;

    ///////  You shouldn't have to modify anything below this line except for
    ///////  the function getRandomBytes().
    //
    // Note: in the following code the two dimensional arrays are indexed as
    //       you would probably expect, as array[row][column]. The state arrays
    //       are 2d arrays of the form state[4][Nb].


    // The number of rounds for the cipher, indexed by [Nk][Nb]
    var roundsArray = [ ,,,,[,,,,10,, 12,, 14],, 
                            [,,,,
    12,, 12,, 14],, 
                            [,,,,
    14,, 14,, 14] ];

    // The number of bytes to shift by in shiftRow, indexed by [Nb][row]
    var shiftOffsets = [ ,,,,[,123],,[,123],,[,134] ];

    // The round constants used in subkey expansion
    var Rcon = [ 
    0x010x020x040x080x100x20
    0x400x800x1b0x360x6c0xd8
    0xab0x4d0x9a0x2f0x5e0xbc
    0x630xc60x970x350x6a0xd4
    0xb30x7d0xfa0xef0xc50x91 ];

    // Precomputed lookup table for the SBox
    var SBox = [
     
    99124119123242107111197,  48,   1103,  43254215171
    118202130201125250,  89,  71240173212162175156164
    114192183253147,  38,  54,  63247204,  52165229241113
    216,  49,  21,   4199,  35195,  24150,   5154,   7,  18128226
    235,  39178117,   9131,  44,  26,  27110,  90160,  82,  59214
    179,  41227,  47132,  83209,   0237,  32252177,  91106203
    190,  57,  74,  76,  88207208239170251,  67,  77,  51133,  69
    249,   2127,  80,  60159168,  81163,  64143146157,  56245
    188182218,  33,  16255243210205,  12,  19236,  95151,  68,  
    23,  196167126,  61100,  93,  25115,  96129,  79220,  34,  42
    144136,  70238184,  20222,  94,  11219224,  50,  58,  10,  73,
      
    6,  36,  92194211172,  98145149228121231200,  55109
    141213,  78169108,  86244234101122174,   8186120,  37,  
     
    46,  28166180198232221116,  31,  75189139138112,  62
    181102,  72,   3246,  14,  97,  53,  87185134193,  29158225,
    248152,  17105217142148155,  30135233206,  85,  40223,
    140161137,  13191230,  66104,  65153,  45,  15176,  84187,  
     
    22 ];

    // Precomputed lookup table for the inverse SBox
    var SBoxInverse = [
     
    82,   9106213,  48,  54165,  56191,  64163158129243215
    251124227,  57130155,  47255135,  52142,  67,  68196222
    233203,  84123148,  50166194,  35,  61238,  76149,  11,  66
    250195,  78,   8,  46161102,  40217,  36178118,  91162,  73
    109139209,  37114248246100134104152,  22212164,  92
    204,  93101182146108112,  72,  80253237185218,  94,  21,  
     
    70,  87167141157132144216171,   0140188211,  10247
    228,  88,   5184179,  69,   6208,  44,  30143202,  63,  15,   2
    193175189,   3,   1,  19138107,  58145,  17,  65,  79103220
    234151242207206240180230115150172116,  34231173,
     
    53133226249,  55232,  28117223110,  71241,  26113,  29
     
    41197137111183,  98,  14170,  24190,  27252,  86,  62,  75
    198210121,  32154219192254120205,  90244,  31221168,
     
    51136,   7199,  49177,  18,  16,  89,  39128236,  95,  96,  81,
    127169,  25181,  74,  13,  45229122159147201156239160,
    224,  59,  77174,  42245176200235187,  60131,  83153,  97
     
    23,  43,   4126186119214,  38225105,  20,  99,  85,  33,  12,
    125 ];

    // This method circularly shifts the array left by the number of elements
    // given in its parameter. It returns the resulting array and is used for 
    // the ShiftRow step. Note that shift() and push() could be used for a more 
    // elegant solution, but they require IE5.5+, so I chose to do it manually. 

    function cyclicShiftLeft(theArraypositions) {
      var 
    temp theArray.slice(0positions);
      
    theArray theArray.slice(positions).concat(temp);
      return 
    theArray;
    }

    // Cipher parameters ... do not change these
    var Nk keySizeInBits 32;                   
    var 
    Nb blockSizeInBits 32;
    var 
    Nr roundsArray[Nk][Nb];

    // Multiplies the element "poly" of GF(2^8) by x. See the Rijndael spec.

    function xtime(poly) {
      
    poly <<= 1;
      return ((
    poly 0x100) ? (poly 0x11B) : (poly));
    }

    // Multiplies the two elements of GF(2^8) together and returns the result.
    // See the Rijndael spec, but should be straightforward: for each power of
    // the indeterminant that has a 1 coefficient in x, add y times that power
    // to the result. x and y should be bytes representing elements of GF(2^8)

    function mult_GF256(xy) {
      var 
    bitresult 0;
      
      for (
    bit 1bit 256bit *= 2xtime(y)) {
        if (
    bit
          
    result ^= y;
      }
      return 
    result;
    }

    // Performs the substitution step of the cipher. State is the 2d array of
    // state information (see spec) and direction is string indicating whether
    // we are performing the forward substitution ("encrypt") or inverse 
    // substitution (anything else)

    function byteSub(statedirection) {
      var 
    S;
      if (
    direction == "encrypt")           // Point S to the SBox we're using
        
    SBox;
      else
        
    SBoxInverse;
      for (var 
    04i++)           // Substitute for every byte in state
        
    for (var 0Nbj++)
           
    state[i][j] = S[state[i][j]];
    }

    // Performs the row shifting step of the cipher.

    function shiftRow(statedirection) {
      for (var 
    i=1i<4i++)               // Row 0 never shifts
        
    if (direction == "encrypt")
           
    state[i] = cyclicShiftLeft(state[i], shiftOffsets[Nb][i]);
        else
           
    state[i] = cyclicShiftLeft(state[i], Nb shiftOffsets[Nb][i]);

    }

    // Performs the column mixing step of the cipher. Most of these steps can
    // be combined into table lookups on 32bit values (at least for encryption)
    // to greatly increase the speed. 

    function mixColumn(statedirection) {
      var 
    = [];                            // Result of matrix multiplications
      
    for (var 0Nbj++) {         // Go through each column...
        
    for (var 04i++) {        // and for each row in the column...
          
    if (direction == "encrypt")
            
    b[i] = mult_GF256(state[i][j], 2) ^          // perform mixing
                   
    mult_GF256(state[(i+1)%4][j], 3) ^ 
                   
    state[(i+2)%4][j] ^ 
                   
    state[(i+3)%4][j];
          else 
            
    b[i] = mult_GF256(state[i][j], 0xE) ^ 
                   
    mult_GF256(state[(i+1)%4][j], 0xB) ^
                   
    mult_GF256(state[(i+2)%4][j], 0xD) ^
                   
    mult_GF256(state[(i+3)%4][j], 9);
        }
        for (var 
    04i++)          // Place result back into column
          
    state[i][j] = b[i];
      }
    }

    // Adds the current round key to the state information. Straightforward.

    function addRoundKey(stateroundKey) {
      for (var 
    0Nbj++) {                 // Step through columns...
        
    state[0][j] ^= (roundKey[j] & 0xFF);         // and XOR
        
    state[1][j] ^= ((roundKey[j]>>8) & 0xFF);
        
    state[2][j] ^= ((roundKey[j]>>16) & 0xFF);
        
    state[3][j] ^= ((roundKey[j]>>24) & 0xFF);
      }
    }

    // This function creates the expanded key from the input (128/192/256-bit)
    // key. The parameter key is an array of bytes holding the value of the key.
    // The returned value is an array whose elements are the 32-bit words that 
    // make up the expanded key.

    function keyExpansion(key) {
      var 
    expandedKey = new Array();
      var 
    temp;

      
    // in case the key size or parameters were changed...
      
    Nk keySizeInBits 32;                   
      
    Nb blockSizeInBits 32;
      
    Nr roundsArray[Nk][Nb];

      for (var 
    j=0Nkj++)     // Fill in input key first
        
    expandedKey[j] = 
          (
    key[4*j]) | (key[4*j+1]<<8) | (key[4*j+2]<<16) | (key[4*j+3]<<24);

      
    // Now walk down the rest of the array filling in expanded key bytes as
      // per Rijndael's spec
      
    for (NkNb * (Nr 1); j++) {    // For each word of expanded key
        
    temp expandedKey[1];
        if (
    Nk == 0
          
    temp = ( (SBox[(temp>>8) & 0xFF]) |
                   (
    SBox[(temp>>16) & 0xFF]<<8) |
                   (
    SBox[(temp>>24) & 0xFF]<<16) |
                   (
    SBox[temp 0xFF]<<24) ) ^ Rcon[Math.floor(Nk) - 1];
        else if (
    Nk && Nk == 4)
          
    temp = (SBox[(temp>>24) & 0xFF]<<24) |
                 (
    SBox[(temp>>16) & 0xFF]<<16) |
                 (
    SBox[(temp>>8) & 0xFF]<<8) |
                 (
    SBox[temp 0xFF]);
        
    expandedKey[j] = expandedKey[j-Nk] ^ temp;
      }
      return 
    expandedKey;
    }

    // Rijndael's round functions... 

    function Round(stateroundKey) {
      
    byteSub(state"encrypt");
      
    shiftRow(state"encrypt");
      
    mixColumn(state"encrypt");
      
    addRoundKey(stateroundKey);
    }

    function 
    InverseRound(stateroundKey) {
      
    addRoundKey(stateroundKey);
      
    mixColumn(state"decrypt");
      
    shiftRow(state"decrypt");
      
    byteSub(state"decrypt");
    }

    function 
    FinalRound(stateroundKey) {
      
    byteSub(state"encrypt");
      
    shiftRow(state"encrypt");
      
    addRoundKey(stateroundKey);
    }

    function 
    InverseFinalRound(stateroundKey){
      
    addRoundKey(stateroundKey);
      
    shiftRow(state"decrypt");
      
    byteSub(state"decrypt");  
    }

    // encrypt is the basic encryption function. It takes parameters
    // block, an array of bytes representing a plaintext block, and expandedKey,
    // an array of words representing the expanded key previously returned by
    // keyExpansion(). The ciphertext block is returned as an array of bytes.

    function encrypt(blockexpandedKey) {
      var 
    i;  
      if (!
    block || block.length*!= blockSizeInBits)
         return; 
      if (!
    expandedKey)
         return;

      
    block packBytes(block);
      
    addRoundKey(blockexpandedKey);
      for (
    i=1i<Nri++) 
        
    Round(blockexpandedKey.slice(Nb*iNb*(i+1)));
      
    FinalRound(blockexpandedKey.slice(Nb*Nr)); 
      return 
    unpackBytes(block);
    }

    // decrypt is the basic decryption function. It takes parameters
    // block, an array of bytes representing a ciphertext block, and expandedKey,
    // an array of words representing the expanded key previously returned by
    // keyExpansion(). The decrypted block is returned as an array of bytes.

    function decrypt(blockexpandedKey) {
      var 
    i;
      if (!
    block || block.length*!= blockSizeInBits)
         return;
      if (!
    expandedKey)
         return;

      
    block packBytes(block);
      
    InverseFinalRound(blockexpandedKey.slice(Nb*Nr)); 
      for (
    Nr 1i>0i--) 
        
    InverseRound(blockexpandedKey.slice(Nb*iNb*(i+1)));
      
    addRoundKey(blockexpandedKey);
      return 
    unpackBytes(block);
    }

    // This method takes a byte array (byteArray) and converts it to a string by
    // applying String.fromCharCode() to each value and concatenating the result.
    // The resulting string is returned. Note that this function SKIPS zero bytes
    // under the assumption that they are padding added in formatPlaintext().
    // Obviously, do not invoke this method on raw data that can contain zero
    // bytes. It is really only appropriate for printable ASCII/Latin-1 
    // values. Roll your own function for more robust functionality :)

    function byteArrayToString(byteArray) {
      var 
    result "";
      for(var 
    i=0i<byteArray.lengthi++)
        if (
    byteArray[i] != 0
          
    result += String.fromCharCode(byteArray[i]);
      return 
    result;
    }

    // This function takes an array of bytes (byteArray) and converts them
    // to a hexadecimal string. Array element 0 is found at the beginning of 
    // the resulting string, high nibble first. Consecutive elements follow
    // similarly, for example [16, 255] --> "10ff". The function returns a 
    // string.

    function byteArrayToHex(byteArray) {
      var 
    result "";
      if (!
    byteArray)
        return;
      for (var 
    i=0i<byteArray.lengthi++)
        
    result += ((byteArray[i]<16) ? "0" "") + byteArray[i].toString(16);

      return 
    result;
    }

    // This function converts a string containing hexadecimal digits to an 
    // array of bytes. The resulting byte array is filled in the order the
    // values occur in the string, for example "10FF" --> [16, 255]. This
    // function returns an array. 

    function hexToByteArray(hexString) {
      var 
    byteArray = [];
      if (
    hexString.length 2)             // must have even length
        
    return;
      if (
    hexString.indexOf("0x") == || hexString.indexOf("0X") == 0)
        
    hexString hexString.substring(2);
      for (var 
    0i<hexString.length+= 2
        
    byteArray[Math.floor(i/2)] = parseInt(hexString.slice(ii+2), 16);
      return 
    byteArray;
    }

    // This function packs an array of bytes into the four row form defined by
    // Rijndael. It assumes the length of the array of bytes is divisible by
    // four. Bytes are filled in according to the Rijndael spec (starting with
    // column 0, row 0 to 3). This function returns a 2d array.

    function packBytes(octets) {
      var 
    state = new Array();
      if (!
    octets || octets.length 4)
        return;

      
    state[0] = new Array();  state[1] = new Array(); 
      
    state[2] = new Array();  state[3] = new Array();
      for (var 
    j=0j<octets.lengthj+= 4) {
         
    state[0][j/4] = octets[j];
         
    state[1][j/4] = octets[j+1];
         
    state[2][j/4] = octets[j+2];
         
    state[3][j/4] = octets[j+3];
      }
      return 
    state;  
    }

    // This function unpacks an array of bytes from the four row format preferred
    // by Rijndael into a single 1d array of bytes. It assumes the input "packed"
    // is a packed array. Bytes are filled in according to the Rijndael spec. 
    // This function returns a 1d array of bytes.

    function unpackBytes(packed) {
      var 
    result = new Array();
      for (var 
    j=0j<packed[0].lengthj++) {
        
    result[result.length] = packed[0][j];
        
    result[result.length] = packed[1][j];
        
    result[result.length] = packed[2][j];
        
    result[result.length] = packed[3][j];
      }
      return 
    result;
    }

    // This function takes a prospective plaintext (string or array of bytes)
    // and pads it with zero bytes if its length is not a multiple of the block 
    // size. If plaintext is a string, it is converted to an array of bytes
    // in the process. The type checking can be made much nicer using the 
    // instanceof operator, but this operator is not available until IE5.0 so I 
    // chose to use the heuristic below. 

    function formatPlaintext(plaintext) {
      var 
    bpb blockSizeInBits 8;               // bytes per block
      
    var i;

      
    // if primitive string or String instance
      
    if (typeof plaintext == "string" || plaintext.indexOf) {
        
    plaintext plaintext.split("");
        
    // Unicode issues here (ignoring high byte)
        
    for (i=0i<plaintext.lengthi++)
          
    plaintext[i] = plaintext[i].charCodeAt(0) & 0xFF;
      } 

      for (
    bpb - (plaintext.length bpb); && bpbi--) 
        
    plaintext[plaintext.length] = 0;
      
      return 
    plaintext;
    }

    // Returns an array containing "howMany" random bytes. YOU SHOULD CHANGE THIS
    // TO RETURN HIGHER QUALITY RANDOM BYTES IF YOU ARE USING THIS FOR A "REAL"
    // APPLICATION.

    function getRandomBytes(howMany) {
      var 
    i;
      var 
    bytes = new Array();
      for (
    i=0i<howManyi++)
        
    bytes[i] = Math.round(Math.random()*255);
      return 
    bytes;
    }

    // rijndaelEncrypt(plaintext, key, mode)
    // Encrypts the plaintext using the given key and in the given mode. 
    // The parameter "plaintext" can either be a string or an array of bytes. 
    // The parameter "key" must be an array of key bytes. If you have a hex 
    // string representing the key, invoke hexToByteArray() on it to convert it 
    // to an array of bytes. The third parameter "mode" is a string indicating
    // the encryption mode to use, either "ECB" or "CBC". If the parameter is
    // omitted, ECB is assumed.
    // 
    // An array of bytes representing the cihpertext is returned. To convert 
    // this array to hex, invoke byteArrayToHex() on it. If you are using this 
    // "for real" it is a good idea to change the function getRandomBytes() to 
    // something that returns truly random bits.

    function rijndaelEncrypt(plaintextkeymode) {
      var 
    expandedKeyiaBlock;
      var 
    bpb blockSizeInBits 8;          // bytes per block
      
    var ct;                                 // ciphertext

      
    if (!plaintext || !key)
        return;
      if (
    key.length*!= keySizeInBits)
        return; 
      if (
    mode == "CBC")
        
    ct getRandomBytes(bpb);             // get IV
      
    else {
        
    mode "ECB";
        
    ct = new Array();
      }

      
    // convert plaintext to byte array and pad with zeros if necessary. 
      
    plaintext formatPlaintext(plaintext);

      
    expandedKey keyExpansion(key);
      
      for (var 
    block=0block<plaintext.length bpbblock++) {
        
    aBlock plaintext.slice(block*bpb, (block+1)*bpb);
        if (
    mode == "CBC")
          for (var 
    i=0i<bpbi++) 
            
    aBlock[i] ^= ct[block*bpb i];
        
    ct ct.concat(encrypt(aBlockexpandedKey));
      }

      return 
    ct;
    }

    // rijndaelDecrypt(ciphertext, key, mode)
    // Decrypts the using the given key and mode. The parameter "ciphertext" 
    // must be an array of bytes. The parameter "key" must be an array of key 
    // bytes. If you have a hex string representing the ciphertext or key, 
    // invoke hexToByteArray() on it to convert it to an array of bytes. The
    // parameter "mode" is a string, either "CBC" or "ECB".
    // 
    // An array of bytes representing the plaintext is returned. To convert 
    // this array to a hex string, invoke byteArrayToHex() on it. To convert it 
    // to a string of characters, you can use byteArrayToString().

    function rijndaelDecrypt(ciphertextkeymode) {
      var 
    expandedKey;
      var 
    bpb blockSizeInBits 8;          // bytes per block
      
    var pt = new Array();                   // plaintext array
      
    var aBlock;                             // a decrypted block
      
    var block;                              // current block number

      
    if (!ciphertext || !key || typeof ciphertext == "string")
        return;
      if (
    key.length*!= keySizeInBits)
        return; 
      if (!
    mode)
        
    mode "ECB";                         // assume ECB if mode omitted

      
    expandedKey keyExpansion(key);
     
      
    // work backwards to accomodate CBC mode 
      
    for (block=(ciphertext.length bpb)-1block>0block--) {
        
    aBlock 
         
    decrypt(ciphertext.slice(block*bpb,(block+1)*bpb), expandedKey);
        if (
    mode == "CBC"
          for (var 
    i=0i<bpbi++) 
            
    pt[(block-1)*bpb i] = aBlock[i] ^ ciphertext[(block-1)*bpb i];
        else 
          
    pt aBlock.concat(pt);
      }

      
    // do last block if ECB (skips the IV in CBC)
      
    if (mode == "ECB")
        
    pt decrypt(ciphertext.slice(0bpb), expandedKey).concat(pt);

      return 
    pt;
    }