什麼是 AES 對稱加密演算法?PHP 與 NodeJS 範例實作

什麼是 AES 對稱加密演算法?

AES(Advanced Encryption Standard)是一種對稱加密算法,它是目前最常用的加密算法之一。它的目的是保護數據的安全性和隱私,可以應用於各種場合,例如在互聯網上安全傳輸數據,存儲敏感數據等等。

AES算法使用相同的密鑰來加密和解密數據,因此被稱為“對稱加密算法”。它的加密過程包括以下步驟:

  1. 密鑰擴展:將輸入的密鑰擴展為更長的密鑰序列,以供後續的加密和解密過程使用。
  2. 初始輪:將明文數據按照一定的規則與密鑰序列進行異或操作。
  3. 輪函數:重覆執行多輪操作,每一輪都包括四個步驟:
    • 字節替換:將數據中的每個字節替換為另一個字節,使用一個固定的S盒進行映射。
    • 行移位:將數據矩陣中的每一行循環左移不同的偏移量。
    • 列混淆:將數據矩陣中的每一列進行混淆。
    • 密鑰加:將密鑰序列中的一部分與數據進行異或操作。
  4. 最終輪:最後一輪操作中,省略列混淆步驟,只包括字節替換、行移位和密鑰加。
  5. 輸出:輸出加密後的數據。

解密過程與加密過程類似,只是在輪函數中的操作順序和密鑰序列不同。

AES算法可以使用不同的密鑰長度和不同的輪數來進行加密,較長的密鑰和更多的輪數可以提高安全性,但也會增加計算覆雜度。目前,AES算法支持128位、192位和256位密鑰長度,以及10、12和14輪加密操作。

PHP 實作 AES 加解密

PHP 實作 AES 加密 (encrypt.php)

<?php
// 定義要加密的字串
$text = "這是一個需要加密的字串";

// 定義密鑰和 IV
$key = "0123456789abcdef0123456789abcdef";
$iv = "0000000000000000";

// 定義加密方式、加密模式、填充方式
$cipher = "aes-256-cbc";
$options = OPENSSL_RAW_DATA;

// 加密字串
$encrypted = openssl_encrypt($text, $cipher, $key, $options, $iv);

// 轉換加密後的字串成為 base64 格式
$encrypted_base64 = base64_encode($encrypted);

// 輸出加密後的字串
echo $encrypted_base64;

PHP 實作 AES 解密 (descrypt.php)

<?php

// 定義要解密的字串
$encrypted_base64 = "0EmVS69YUBCibylCWo3SFW5qtM7YU/rRXHnrIsewMErikpR8y9ZfnNjm/zYh8GCA";

// 定義密鑰和 IV
$key = "0123456789abcdef0123456789abcdef";
$iv = "0000000000000000";

// 定義加密方式、加密模式、填充方式
$cipher = "aes-256-cbc";
$options = OPENSSL_RAW_DATA;

// 將加密後的字串解碼成二進位資料
$encrypted = base64_decode($encrypted_base64);

// 解密字串
$decrypted = openssl_decrypt($encrypted, $cipher, $key, $options, $iv);

// 輸出解密後的字串
echo $decrypted;

NodeJS 實作 AES 加解密

NodeJS 實作 AES 加密 (encrypt.js)

var CryptoJS = require("crypto-js");

// 定義要加密的字串
const text = "這是一個需要加密的字串";

const key = '0123456789abcdef0123456789abcdef'
const iv = '0000000000000000'

const cipher = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv), // parse the IV 
    padding: CryptoJS.pad.Pkcs7,
    mode: CryptoJS.mode.CBC
})

console.log(cipher.toString())

NodeJS 實作 AES 解密 (decrypt.js)

var CryptoJS = require("crypto-js");

const key = '0123456789abcdef0123456789abcdef'
const iv = '0000000000000000'

const ciphertext = CryptoJS.enc.Base64.parse("0EmVS69YUBCibylCWo3SFW5qtM7YU/rRXHnrIsewMErikpR8y9ZfnNjm/zYh8GCA");
const encryptedCP = CryptoJS.lib.CipherParams.create({
  ciphertext: ciphertext,
  formatter: CryptoJS.format.OpenSSL
});

const decryptedWA = CryptoJS.AES.decrypt(
    encryptedCP,
    CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv)
});

const decryptedUtf8 = decryptedWA.toString(CryptoJS.enc.Utf8);

console.log(decryptedUtf8)

瀏覽器內建 TextEncoder/TexrDecoder 實作

瀏覽器內建 TextEncoder/TexrDecoder 實作加密

        // Convert the data from a UTF-8 string to a byte array
        const data = new TextEncoder().encode("這是一個需要加密的字串")
        // Convert the key and IV from UTF-8 strings to byte arrays
        const key = new TextEncoder().encode('0123456789abcdef0123456789abcdef');
        const iv = new TextEncoder().encode('0000000000000000');

        // Create a new AES cipher with key and IV
        window.crypto.subtle.importKey(
            "raw",
            key,
            { name: "AES-CBC", length: 256 },
            false,
            ["encrypt"]
        ).then(function(cipher) {
            // Pad the data to a multiple of 16 bytes
            const padding = 16 - (data.length % 16);
            const paddedData = new Uint8Array(data.length + padding);
            paddedData.set(data);
            paddedData.fill(padding, data.length);

            // Encrypt the padded data using the AES cipher and CBC mode
            window.crypto.subtle.encrypt(
                { name: "AES-CBC", iv },
                cipher,
                paddedData
            ).then(function (encryptedDataBuffer) {
                const encrypted = btoa(String.fromCharCode(...new Uint8Array(encryptedDataBuffer)))
                console.log(encrypted)
            });
        });

瀏覽器內建 TextEncoder/TexrDecoder 實作解密

        var encryptedData = "0EmVS69YUBCibylCWo3SFW5qtM7YU/rRXHnrIsewMErikpR8y9ZfnNjm/zYh8GCA"
        const key = new TextEncoder().encode('0123456789abcdef0123456789abcdef')
        const iv = new TextEncoder().encode('0000000000000000')

        window.crypto.subtle.importKey(
            "raw",
            key,
            { name: "AES-CBC", length: 256 },
            false,
            ["decrypt"]
        ).then(function(cipher) {
            encryptedData = atob(encryptedData);
            const encryptedDataArray = new Uint8Array(
                encryptedData.length
            );
            for (let i = 0; i < encryptedData.length; i++) {
                encryptedDataArray[i] = encryptedData.charCodeAt(i);
            }
            window.crypto.subtle.decrypt(
                { name: "AES-CBC", iv },
                cipher,
                encryptedDataArray
            ).then(function (decryptedDataBuffer) {
                console.log(new TextDecoder().decode(decryptedDataBuffer))
            })
        })

References

延伸推薦