AES 簡介 以及 C# 和 js 實現【加密知多少系列】

2023-03-18 06:00:27

〇、AES 簡介

AES 的全稱是 Advanced Encryption Standard,意思是高階加密標準。它的出現主要是為了取代 DES(Data Encryption StandardData Encryption Standard)加密演演算法的,因為我們都知道 DES 演演算法的金鑰長度是 56Bit,因此演演算法的理論安全強度是 2 的 56 次方。雖然出現了 3DES 的加密方法,但由於它的加密時間是 DES 演演算法的 3 倍多,64Bit 的分組大小相對較小,所以還是不能滿足人們對安全性的要求。於是 1997 年 1 月 2 號,美國國家標準技術研究所宣佈希望徵集高階加密標準,最終經過安全性分析、軟硬體效能評估等嚴格的步驟,Rijndael 演演算法獲勝。加密演演算法的要求是:

  • 分組大小為 128 位的分組密碼。
  • 必須支援三種密碼標準:128 位、192 位和 256 位。
  • 比提交的其他演演算法更安全。
  • 在軟體和硬體實現上都很高效。

AES 密碼與分組密碼 Rijndael 基本上完全一致,Rijndael 分組大小和金鑰大小都可以為 128 位、192 位和256 位。然而 AES 只要求分組大小為 128 位,因此只有分組長度為 128Bit 的 Rijndael 才稱為 AES 演演算法。金鑰長度為 192 位和 256 位的處理方式和 128 位的處理方式類似,只不過金鑰長度每增加 64 位,演演算法的迴圈次數就增加 2 輪,128 位迴圈 10 輪、192 位迴圈 12 輪、256 位迴圈 14 輪。

關於 AES 演演算法參考: 密碼學基礎:AES加密演演算法

AES 其中常用的模式就是 ECB 和 CBC,優缺點如下:

ECB 模式(適合加密小訊息)(Electronic Code Book:電子密碼本)

  優點:

  • 簡單;
  • 有利於平行計算;
  • 誤差不會被傳送;
  • 不需要初始化向量 IV

  缺點:

  • 不能隱藏明文的模式;
  • 可能對明文進行主動攻擊。

CBC 模式:(適合機密比較長的訊息)(Cipher Block Chaining:加密塊鏈)

  優點:

  • 不容易主動攻擊,安全性好於 ECB;
  • 適合傳輸長度長的報文,是 SSL、IPSec 的標準。

  缺點:

  • 不利於平行計算;
  • 誤差傳遞;
  • 需要初始化向量 IV

注意:另外還有兩種模式(OFB-Output FeedBack-輸出反饋、CFB-Cipher FeedBack Mode-加密反饋),本文不再介紹。

加密模式參考:分組對稱加密模式:ECB/CBC/CFB/OFB缺CTR

一、C# 程式碼實現

注意:本範例輸出的是 Base64 編碼格式結果,若需其他格式,可在最後密文輸出時,修改Convert.ToBase64String()方法,然後解密時修改Convert.FromBase64String()方法。

// 測試 (注意:金鑰的長度必須是 16/24/32)
string miwen16 = SecurityAES.AesEncrypt("TestString", "1111122222333334"); // rNSr6EVnwFlIbr43jm5pvQ==
string miwen24 = SecurityAES.AesEncrypt("TestString", "111112222233333444445555"); // YDYadzDDM5b6EVfi3EUVIQ==
string miwen32 = SecurityAES.AesEncrypt("TestString", "11111222223333344444555556666677"); // K+fDhtmqpkwWZL4kByCLFQ==

/// <summary>
/// AES 加密
/// </summary>
/// <param name="aeseninstr">待加密字串</param>
/// <param name="secretkey">金鑰</param>
/// <returns></returns>
public static string AesEncrypt(string aeseninstr, string secretkey)
{
    int[] digitlist = { 16, 24, 32 };
    if (string.IsNullOrEmpty(aeseninstr) ||string.IsNullOrEmpty(secretkey)|| Array.IndexOf(digitlist, secretkey.Length) <0)
        return null;
    byte[] toEncryptArray = Encoding.UTF8.GetBytes(aeseninstr);
    RijndaelManaged rm = new RijndaelManaged
    {
        Key = Encoding.UTF8.GetBytes(secretkey),
        Mode = CipherMode.ECB,
        Padding = PaddingMode.PKCS7
        };
    ICryptoTransform cTransform = rm.CreateEncryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    return Convert.ToBase64String(resultArray);
}

/// <summary>
/// AES 解密
/// </summary>
/// <param name="aesdeinstr">密文</param>
/// <param name="secretkey">金鑰</param>
/// <returns></returns>
public static string AesDecrypt(string aesdeinstr, string secretkey)
{
    int[] digitlist = { 16, 24, 32 };
    if (string.IsNullOrEmpty(aesdeinstr) || string.IsNullOrEmpty(secretkey) || Array.IndexOf(digitlist, secretkey.Length) < 0)
        return null;
    byte[] toEncryptArray = Convert.FromBase64String(aesdeinstr);
    RijndaelManaged rm = new RijndaelManaged
    {
        Key = Encoding.UTF8.GetBytes(secretkey),
        Mode = CipherMode.ECB,
        Padding = PaddingMode.PKCS7
        };
    ICryptoTransform cTransform = rm.CreateDecryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    return Encoding.UTF8.GetString(resultArray);
}

二、js 語言實現

1、通過參照 crypto-js 實現(操作簡單,推薦使用)

// 引入 crypto-js
<script src="http://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/crypto-js.js"></script>
// npm 方式
> npm install crypto-js
// 呼叫方法 message() 檢視結果
// 注意:加解密的編碼型別需相同,本範例統一採用 UTF-8
function message(){
    outdata_value = AES_ECB_ENCRYPT("TestString", "1111122222333334");
    alert(outdata_value)
    console.log("outdata_value-aes_encrypt:", outdata_value);
    outdata_value = AES_ECB_DECRYPT(outdata_value, "1111122222333334");
    alert(outdata_value)
    console.log("outdata_value-aes_decrypt:", outdata_value);
}
// 加密
function AES_ECB_ENCRYPT(text, secretKey) {
    var keyHex = CryptoJS.enc.Utf8.parse(secretKey);
    var messageHex = CryptoJS.enc.Utf8.parse(text);
    var encrypted = CryptoJS.AES.encrypt(text, keyHex, {
        "mode": CryptoJS.mode.ECB,
        "padding": CryptoJS.pad.Pkcs7
    });
    return encrypted.toString();
}
// 解密
function AES_ECB_DECRYPT(textBase64, secretKey) {
    var keyHex = CryptoJS.enc.Utf8.parse(secretKey);
    var decrypt = CryptoJS.AES.decrypt(textBase64, keyHex, {
        "mode": CryptoJS.mode.ECB,
        "padding": CryptoJS.pad.Pkcs7
    });
    return CryptoJS.enc.Utf8.stringify(decrypt);
}

2、純 js 方法的實現

注意:經測試也是可以用的,但是還需要進一步優化,現只支援待加密字元和金鑰均為 16 位。

呼叫 message() 方法檢視效果。

控制檯輸出:

點選檢視 js 語言實現
let outdata_value = "";
let indata_value = "czzj.zfy.acomfei";
let key_value = strtoascii("1111122222333334"); // 49 49 49 49 49 50 50 50 50 50 51 51 51 51 51 52

function message(){
    indata_value = stringToHex(indata_value); // 轉為 Hex 型別
    console.log("indata_value1:",indata_value); // 66 65 69 6e 69 61 6f 6d 79 2e 63 6f 6d 66 65 69
    aes_encrypt();
    alert(outdata_value) // d1 63 4a fa 0c da da d1 14 e0 fc 63 f1 75 02 cf
    console.log("outdata_value-aes_encrypt:", outdata_value);

    indata_value = outdata_value;
    console.log("indata_value2:",indata_value);
    aes_decrypt();
    console.log("outdata_value-aes_decrypt:",outdata_value); // 63 7a 7a 6a 2e 7a 66 79 2e 61 63 6f 6d 66 65 69
    alert(hexCharCodeToStr(outdata_value)); // 將 UTF-8 編碼的 Hex 型別轉為 string
}

// 將 string 轉為 Hex
function stringToHex(str) {
    var val = ""
    for (var i = 0; i < str.length; i++) {
        if (val == "")
            val = str.charCodeAt(i).toString(16)
        else
            val += " " + str.charCodeAt(i).toString(16)
    }
    return val
}
// 將以 UTF-8 編碼的位元組序列解碼為 String
function hexCharCodeToStr(hex) {
    console.log("hexCharCodeToStr-hex:",hex);
    // 將每一位元組都轉換成 % 加 16 進位制數位的表示形式
    // 再通過 decodeURIComponent 方法解碼,得到相應的字串
    var bytes = hex.split(" ")
    var encoded = "";
    for (var i = 0; i < bytes.length; i++) {
        encoded += '%' + bytes[i].toString(16)
    }
    return decodeURIComponent(encoded)
}
// 將 string 轉為以 UTF-8 編碼的位元組序列
function encodeUtf8(text) {
    const code = encodeURIComponent(text);
    const bytes = [];
    for (var i = 0; i < code.length; i++) {
        const c = code.charAt(i);
        if (c === '%') {
            const hex = code.charAt(i + 1) + code.charAt(i + 2);
            const hexVal = parseInt(hex, 16);
            bytes.push(hexVal);
            i += 2;
        } else bytes.push(c.charCodeAt(0));
    }
    return bytes;
}
// 將字串轉為 ASCII 型別,fix 為自定義分隔符
function strtoascii(str, fix = ' ') {
    if (str.length < 1)
        return false;
    var arr = str.split("");
    var txt = '';
    arr.forEach(function (v, i) {
        txt += fix + v.charCodeAt();
    });
    console.log("strtoascii-txt:",txt)
    return txt;
}



// accumulate values to put into text area
var accumulated_output_info;
// add a labeled value to the text area
function accumulate_output(str) {
    accumulated_output_info = accumulated_output_info + str + "\n";
}
// convert a 8-bit value to a string
function cvt_hex8(val) {
    var vh = (val >>> 4) & 0x0f;
    return vh.toString(16) + (val & 0x0f).toString(16);
}
// convert a 32-bit value to a 8-char hex string
function cvt_hex32(val) {
    var str = "";
    var i;
    var v;

    for (i = 7; i >= 0; i--) {
        v = (val >>> (i * 4)) & 0x0f;
        str += v.toString(16);
    }
    return str;
}
// convert a two-digit hex value to a number
function cvt_byte(str) {
    // get the first hex digit
    var val1 = str.charCodeAt(0);
    // do some error checking
    if (val1 >= 48 && val1 <= 57)
        // have a valid digit 0-9
        val1 -= 48;
    else if (val1 >= 65 && val1 <= 70)
        // have a valid digit A-F
        val1 -= 55;
    else if (val1 >= 97 && val1 <= 102)
        // have a valid digit A-F
        val1 -= 87;
    else {
        // not 0-9 or A-F, complain
        window.alert(str.charAt(1) + " is not a valid hex digit");
        return -1;
    }
    // get the second hex digit
    var val2 = str.charCodeAt(1);
    // do some error checking
    if (val2 >= 48 && val2 <= 57)
        // have a valid digit 0-9
        val2 -= 48;
    else if (val2 >= 65 && val2 <= 70)
        // have a valid digit A-F
        val2 -= 55;
    else if (val2 >= 97 && val2 <= 102)
        // have a valid digit A-F
        val2 -= 87;
    else {
        // not 0-9 or A-F, complain
        window.alert(str.charAt(2) + " is not a valid hex digit");
        return -1;
    }
    // all is ok, return the value
    return val1 * 16 + val2;
}
// add a byte to the output
function accumulate_byte(label, val) {
    accumulated_output_info += label + cvt_hex8(val) + "\n";
}
// add a word to the output
function accumulate_wordarray(label, ary) {
    var i, j;
    accumulated_output_info += label + " ";
    // process the four elements in this word
    for (j = 0; j < 4; j++)
        accumulated_output_info += " " + cvt_hex8(ary[j]);
    // mark the end of the word
    accumulated_output_info += "\n";
}
// add an array to the output
function accumulate_array(label, ary) {
    var i, j;
    var spacer = "";
    // build a set of spaces of equal length to the label
    while (spacer.length < label.length)
        spacer += " ";
    // build the table
    for (i = 0; i < 16; i += 4) {
        // add label/spaces
        if (i == 0)
            accumulated_output_info += label + " ";
        else
            accumulated_output_info += spacer + " ";
        // process the four elements in this "row"
        for (j = 0; j < 4; j++)
            accumulated_output_info += " " + cvt_hex8(ary[i + j]);
        // mark the end of this row
        accumulated_output_info += "\n";
    }
}
// S-Box substitution table
var S_enc = new Array(
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16);
// inverse S-Box for decryptions
var S_dec = new Array(
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
    0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
    0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
    0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
    0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
    0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
    0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
    0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
    0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
    0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
    0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
    0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
    0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
    0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
    0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
    0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
    0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d);
// convert two-dimensional indicies to one-dim array indices
var I00 = 0;
var I01 = 1;
var I02 = 2;
var I03 = 3;
var I10 = 4;
var I11 = 5;
var I12 = 6;
var I13 = 7;
var I20 = 8;
var I21 = 9;
var I22 = 10;
var I23 = 11;
var I30 = 12;
var I31 = 13;
var I32 = 14;
var I33 = 15;
// conversion function for non-constant subscripts
// assume subscript range 0..3
function I(x, y) { return (x * 4) + y; }
// remove spaces from input
function remove_spaces(instr) {
    var i;
    var outstr = "";
    for (i = 0; i < instr.length; i++)
        if (instr.charAt(i) != " ")
            // not a space, include it
            outstr += instr.charAt(i);
    return outstr;
}
// get the message to encrypt/decrypt or the key
// return as a 16-byte array
function get_value(lbl, str, isASCII) {
    var dbyte = new Array(16);
    var i;
    var val;    // one hex digit
    if (isASCII) {
        // check length of data
        if (str.length > 16) {
            window.alert(lbl + " is too long, using the first 16 ASCII characters");
        }
        // have ASCII data
        // 16 characters?
        if (str.length >= 16) {
            // 16 or more characters
            for (i = 0; i < 16; i++) {
                dbyte[i] = str.charCodeAt(i);
            }
        }
        else {
            // less than 16 characters - fill with NULLs
            for (i = 0; i < str.length; i++) {
                dbyte[i] = str.charCodeAt(i);
            }
            for (i = str.length; i < 16; i++) {
                dbyte[i] = 0;
            }
        }
    }
    else {
        // have hex data - remove any spaces they used, then convert
        str = remove_spaces(str);
        // check length of data
        if (str.length != 32) {
            window.alert(lbl + " length wrong: Is " + str.length +
                         " hex digits, but must be 128 bits (32 hex digits)");
            dbyte[0] = -1;
            return dbyte;
        }
        for (i = 0; i < 16; i++) {
            // isolate and convert this substring
            dbyte[i] = cvt_byte(str.substr(i * 2, 2));
            if (dbyte[i] < 0) {
                // have an error
                dbyte[0] = -1;
                return dbyte;
            }
        } // for i
    } // if isASCII
    // return successful conversion
    return dbyte;
}
//do the AES GF(2**8) multiplication
// do this by the shift-and-"add" approach
function aes_mul(a, b) {
    var res = 0;
    while (a > 0) {
        if ((a & 1) != 0)
            res = res ^ b;        // "add" to the result
        a >>>= 1;            // shift a to get next higher-order bit
        b <<= 1;            // shift multiplier also
    }
    // now reduce it modulo x**8 + x**4 + x**3 + x + 1
    var hbit = 0x10000;        // bit to test if we need to take action
    var modulus = 0x11b00;    // modulus - XOR by this to change value
    while (hbit >= 0x100) {
        if ((res & hbit) != 0)        // if the high-order bit is set
            res ^= modulus;    // XOR with the modulus

        // prepare for the next loop
        hbit >>= 1;
        modulus >>= 1;
    }
    return res;
}
// apply the S-box substitution to the key expansion
function SubWord(word_ary) {
    var i;
    for (i = 0; i < 16; i++)
        word_ary[i] = S_enc[word_ary[i]];
    return word_ary;
}

// rotate the bytes in a word
function RotWord(word_ary) {
    return new Array(word_ary[1], word_ary[2], word_ary[3], word_ary[0]);
}
// calculate the first item Rcon[i] = { x^(i-1), 0, 0, 0 }
// note we only return the first item
function Rcon(exp) {
    var val = 2;
    var result = 1;
    // remember to calculate x^(exp-1)
    exp--;
    // process the exponent using normal shift and multiply
    while (exp > 0) {
        if ((exp & 1) != 0)
            result = aes_mul(result, val);
        // square the value
        val = aes_mul(val, val);
        // move to the next bit
        exp >>= 1;
    }
    return result;
}
// round key generation
// return a byte array with the expanded key information
function key_expand(key) {
    var temp = new Array(4);
    var i, j;
    var w = new Array(4 * 11);
    // copy initial key stuff
    for (i = 0; i < 16; i++) {
        w[i] = key[i];
    }
    accumulate_wordarray("w[0] = ", w.slice(0, 4));
    accumulate_wordarray("w[1] = ", w.slice(4, 8));
    accumulate_wordarray("w[2] = ", w.slice(8, 12));
    accumulate_wordarray("w[3] = ", w.slice(12, 16));
    // generate rest of key schedule using 32-bit words
    i = 4;
    while (i < 44)        // blocksize * ( rounds + 1 )
    {
        // copy word W[i-1] to temp
        for (j = 0; j < 4; j++)
            temp[j] = w[(i - 1) * 4 + j];
        if (i % 4 == 0) {
            // temp = SubWord(RotWord(temp)) ^ Rcon[i/4];
            temp = RotWord(temp);
            accumulate_wordarray("RotWord()=", temp);
            temp = SubWord(temp);
            accumulate_wordarray("SubWord()=", temp);
            temp[0] ^= Rcon(i >>> 2);
            accumulate_wordarray(" ^ Rcon()=", temp);
        }
        // word = word ^ temp
        for (j = 0; j < 4; j++)
            w[i * 4 + j] = w[(i - 4) * 4 + j] ^ temp[j];
        accumulate_wordarray("w[" + i + "] = ", w.slice(i * 4, i * 4 + 4));
        i++;
    }
    return w;
}
// do S-Box substitution
function SubBytes(state, Sbox) {
    var i;
    for (i = 0; i < 16; i++)
        state[i] = Sbox[state[i]];
    return state;
}
// shift each row as appropriate
function ShiftRows(state) {
    var t0, t1, t2, t3;
    // top row (row 0) isn't shifted
    // next row (row 1) rotated left 1 place
    t0 = state[I10];
    t1 = state[I11];
    t2 = state[I12];
    t3 = state[I13];
    state[I10] = t1;
    state[I11] = t2;
    state[I12] = t3;
    state[I13] = t0;
    // next row (row 2) rotated left 2 places
    t0 = state[I20];
    t1 = state[I21];
    t2 = state[I22];
    t3 = state[I23];
    state[I20] = t2;
    state[I21] = t3;
    state[I22] = t0;
    state[I23] = t1;
    // bottom row (row 3) rotated left 3 places
    t0 = state[I30];
    t1 = state[I31];
    t2 = state[I32];
    t3 = state[I33];
    state[I30] = t3;
    state[I31] = t0;
    state[I32] = t1;
    state[I33] = t2;
    return state;
}
// inverset shift each row as appropriate
function InvShiftRows(state) {
    var t0, t1, t2, t3;
    // top row (row 0) isn't shifted
    // next row (row 1) rotated left 1 place
    t0 = state[I10];
    t1 = state[I11];
    t2 = state[I12];
    t3 = state[I13];
    state[I10] = t3;
    state[I11] = t0;
    state[I12] = t1;
    state[I13] = t2;
    // next row (row 2) rotated left 2 places
    t0 = state[I20];
    t1 = state[I21];
    t2 = state[I22];
    t3 = state[I23];
    state[I20] = t2;
    state[I21] = t3;
    state[I22] = t0;
    state[I23] = t1;
    // bottom row (row 3) rotated left 3 places
    t0 = state[I30];
    t1 = state[I31];
    t2 = state[I32];
    t3 = state[I33];
    state[I30] = t1;
    state[I31] = t2;
    state[I32] = t3;
    state[I33] = t0;
    return state;
}
// process column info
function MixColumns(state) {
    var col;
    var c0, c1, c2, c3;
    for (col = 0; col < 4; col++) {
        c0 = state[I(0, col)];
        c1 = state[I(1, col)];
        c2 = state[I(2, col)];
        c3 = state[I(3, col)];
        // do mixing, and put back into array
        state[I(0, col)] = aes_mul(2, c0) ^ aes_mul(3, c1) ^ c2 ^ c3;
        state[I(1, col)] = c0 ^ aes_mul(2, c1) ^ aes_mul(3, c2) ^ c3;
        state[I(2, col)] = c0 ^ c1 ^ aes_mul(2, c2) ^ aes_mul(3, c3);
        state[I(3, col)] = aes_mul(3, c0) ^ c1 ^ c2 ^ aes_mul(2, c3);
    }
    return state;
}
// inverse process column info
function InvMixColumns(state) {
    var col;
    var c0, c1, c2, c3;
    for (col = 0; col < 4; col++) {
        c0 = state[I(0, col)];
        c1 = state[I(1, col)];
        c2 = state[I(2, col)];
        c3 = state[I(3, col)];
        // do inverse mixing, and put back into array
        state[I(0, col)] = aes_mul(0x0e, c0) ^ aes_mul(0x0b, c1)
        ^ aes_mul(0x0d, c2) ^ aes_mul(0x09, c3);
        state[I(1, col)] = aes_mul(0x09, c0) ^ aes_mul(0x0e, c1)
        ^ aes_mul(0x0b, c2) ^ aes_mul(0x0d, c3);
        state[I(2, col)] = aes_mul(0x0d, c0) ^ aes_mul(0x09, c1)
        ^ aes_mul(0x0e, c2) ^ aes_mul(0x0b, c3);
        state[I(3, col)] = aes_mul(0x0b, c0) ^ aes_mul(0x0d, c1)
        ^ aes_mul(0x09, c2) ^ aes_mul(0x0e, c3);
    }
    return state;
}
// insert subkey information
function AddRoundKey(state, w, base) {
    var col;
    for (col = 0; col < 4; col++) {
        state[I(0, col)] ^= w[base + col * 4];
        state[I(1, col)] ^= w[base + col * 4 + 1];
        state[I(2, col)] ^= w[base + col * 4 + 2];
        state[I(3, col)] ^= w[base + col * 4 + 3];
    }
    return state;
}
// return a transposed array
function transpose(msg) {
    var row, col;
    var state = new Array(16);
    for (row = 0; row < 4; row++)
        for (col = 0; col < 4; col++)
            state[I(row, col)] = msg[I(col, row)];
    return state;
}
// final AES state
var AES_output = new Array(16);
// format AES output
// -- uses the global array DES_output
function format_AES_output() {
    var i;
    var bits;
    var str = "";
    // what type of data do we have to work with?
    if (false)//document.stuff.outtype[0].checked
    {
        // convert each set of bits back to ASCII
        for (i = 0; i < 16; i++)
            str += String.fromCharCode(AES_output[i]);
    }
    else {
        // output hexdecimal data (insert spaces)
        str = cvt_hex8(AES_output[0]);
        for (i = 1; i < 16; i++) {
            str += " " + cvt_hex8(AES_output[i]);
        }
    }
    // copy to textbox
    outdata_value = str;
}
// do encrytion
function aes_encrypt() {
    var w = new Array(44);            // subkey information
    var state = new Array(16);            // working state
    var round;
    accumulated_output_info = "";
    // get the message from the user
    // also check if it is ASCII or hex
    var msg = get_value("Message", indata_value, false);//document.stuff.intype[0].checked
    // problems??
    if (msg[0] < 0) {
        //details_value = accumulated_output_info;
        return;
    }
    accumulate_array("Input bits", msg);
    // get the key from the user
    var key = get_value("Key", key_value, false);
    // problems??
    if (key[0] < 0) {
        //details_value = accumulated_output_info;
        return;
    }
    accumulate_array("Key bits", key);
    // expand the key
    accumulate_output("金鑰擴充套件");
    w = key_expand(key);
    // initial state = message in columns (transposed from what we input)
    state = transpose(msg);
    accumulate_array("Initial state", state);
    // display the round key - Transpose due to the way it is stored/used
    accumulate_output("輪密碼加");
    accumulate_array("Round Key", transpose(w.slice(0, 16)));
    state = AddRoundKey(state, w, 0);
    for (round = 1; round < 10; round++) {
        accumulate_array("Round " + round, state);
        accumulate_output("位元組代替");
        state = SubBytes(state, S_enc);
        accumulate_array("After SubBytes", state);
        accumulate_output("行移位");
        state = ShiftRows(state);
        accumulate_array("After ShiftRows", state);
        accumulate_output("列混淆");
        state = MixColumns(state);
        accumulate_array("After MixColumns", state);
        // display the round key - Transpose due to the way it is stored/used
        accumulate_array("Round Key", transpose(w.slice(round * 4 * 4, round * 16 + 16)));
        accumulate_output("輪密碼加");
        // note here the spec uses 32-bit words, we are using bytes, so an extra *4
        state = AddRoundKey(state, w, round * 4 * 4);
    }
    accumulate_output("位元組代替");
    SubBytes(state, S_enc);
    accumulate_array("After SubBytes", state);
    accumulate_output("行移位");
    ShiftRows(state);
    accumulate_array("After ShiftRows", state);
    accumulate_output("輪密碼加");
    AddRoundKey(state, w, 10 * 4 * 4);
    accumulate_array("Output", state);
    // process output
    AES_output = transpose(state);
    format_AES_output();
    //details_value = accumulated_output_info;
}
// do decryption
function aes_decrypt() {
    var w = new Array(44);            // subkey information
    var state = new Array(16);            // working state
    var round;
    accumulated_output_info = "";        
    //indata_value = utf16ToUtf8(indata_value.replace(" ",""));
    console.log("aes_decrypt-indata_value:",indata_value);
    // get the message from the user
    // also check if it is ASCII or hex
    var msg = get_value("Message", indata_value.toString(), false);//document.stuff.intype[0].checked
    // problems??
    if (msg[0] < 0) {
        //details_value = accumulated_output_info;
        return;
    }
    accumulate_array("Input bits", msg);
    // get the key from the user
    var key = get_value("Key", key_value, false);
    // problems??
    if (key[0] < 0) {
        //details_value = accumulated_output_info;
        return;
    }
    accumulate_array("Key bits", key);
    // expand the key
    w = key_expand(key);
    // initial state = message
    state = transpose(msg);
    accumulate_array("Initial state", state);
    // display the round key - Transpose due to the way it is stored/used
    accumulate_array("Round Key", transpose(w.slice(10 * 4 * 4, 10 * 4 * 4 + 16)));
    state = AddRoundKey(state, w, 10 * 4 * 4);
    for (round = 9; round >= 1; round--) {
        accumulate_array("Round " + round, state);
        state = InvShiftRows(state);
        accumulate_array("After InvShiftRows", state);
        state = SubBytes(state, S_dec);
        accumulate_array("After SubBytes", state);
        // display the round key - Transpose due to the way it is stored/used
        accumulate_array("Round Key", transpose(w.slice(round * 4 * 4, round * 16 + 16)));
        // note here the spec uses 32-bit words, we are using bytes, so an extra *4
        state = AddRoundKey(state, w, round * 4 * 4);
        accumulate_array("After AddRoundKey", state);
        state = InvMixColumns(state);
    }

    InvShiftRows(state);
    accumulate_array("After InvShiftRows", state);
    SubBytes(state, S_dec);
    accumulate_array("After SubBytes", state);
    AddRoundKey(state, w, 0);
    accumulate_array("Output", state);
    // process output
    AES_output = transpose(state);
    format_AES_output();
    //details_value = accumulated_output_info;
}

參考:基於JavaScript的AES演演算法加密解密實現(原始碼)