星期五, 7月 08, 2016

TripleDES/AES(CBC/PKCS7) 在 .NET/Java/PHP/Delphi 加解密互通範例

加解密在不同程式語言中一直給程式設計師很多困擾,

尤其是 .NET 到其他程式語言,

所以寫這篇以 .NET 為基礎,

以 TripleDES/AES 加解密,Cipher Mode 使用 CBC,Padding Mode 使用 PKCS7,

提供 .NET/Java/PHP/Delphi 加解密程式範例:
(備註:因為我沒有 Delphi 環境,所以 Delphi 的範例是摘自朋友 kuraki5336 網誌)

.NET 範例 (TripleDES/DES/AES):
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace Sample
{
    /// <summary>
    /// 對稱式加密模式。
    /// </summary>
    public enum SymmetricMode
    {
        /// <summary>
        /// AES 加密演算法。
        /// </summary>
        AES,
        /// <summary>
        /// DES 加密演算法。
        /// </summary>
        DES,
        /// <summary>
        /// TripleDES 加密演算法。
        /// </summary>
        TripleDES
    }
    /// <summary>
    /// 對稱式加密類別(無法被繼承)。
    /// </summary>
    /// <remarks>本加解密類別是使用 UTF8 進行字串資料的轉換,當加密的資料是由 ASCII 或其他格式編碼,會造成資料無法正確解密。</remarks>
    public sealed class SymmetricCryptoUtility : IDisposable
    {
        /// <summary>
        /// 對稱式加密演算法物件。
        /// </summary>
        private readonly SymmetricAlgorithm Crypto = null;
        /// <summary>
        /// 演算法類型。
        /// </summary>
        private readonly SymmetricMode Mode;
        /// <summary>
        /// 對稱式加密類別建構式(無法被繼承)。
        /// </summary>
        /// <param name="algorithm">演算法類型。</param>
        public SymmetricCryptoUtility(SymmetricMode algorithm)
        {
            this.Mode = algorithm;

            switch (this.Mode)
            {
                case SymmetricMode.AES: this.Crypto = new AesCryptoServiceProvider(); break;
                case SymmetricMode.DES: this.Crypto = new DESCryptoServiceProvider(); break;
                case SymmetricMode.TripleDES: this.Crypto = new TripleDESCryptoServiceProvider(); break;
            }

            if (null == this.Crypto) throw new Exception();
        }
        /// <summary>
        /// 產生要用於該演算法的隨機初始化向量。
        /// </summary>
        /// <returns>向量字串。</returns>
        public string GenerateIV()
        {
            string szResult = String.Empty;

            this.Crypto.GenerateIV();

            szResult = Encoding.UTF8.GetString(this.Crypto.IV);

            return szResult;
        }
        /// <summary>
        /// 產生要用於演算法的隨機金鑰。
        /// </summary>
        /// <returns>金鑰字串。</returns>
        public string GenerateKey()
        {
            string szResult = String.Empty;

            this.Crypto.GenerateKey();

            szResult = Encoding.UTF8.GetString(this.Crypto.Key);

            return szResult;
        }
        /// <summary>
        /// 依據設定的演算法進行資料加密。
        /// </summary>
        /// <param name="originalString">原始未加密的字串。</param>
        /// <param name="key">金鑰字串</param>
        /// <param name="iv">向量字串</param>
        /// <returns>計算出來的加密位元組。</returns>
        public byte[] EncryptToBytes(string originalString, string key, string iv)
        {
            byte[] byResult = null;
            // Set Key and IV.
            this.Crypto.Key = Encoding.UTF8.GetBytes(key);
            this.Crypto.IV = Encoding.UTF8.GetBytes((this.Mode == SymmetricMode.AES ? iv : iv.Substring(0, 8)));
            // Encrypt a string to bytes.
            using (MemoryStream memoryStream = new MemoryStream())
            {
                ICryptoTransform cryptoTransform = null;

                switch (this.Mode)
                {
                    case SymmetricMode.AES: cryptoTransform = ((AesCryptoServiceProvider)this.Crypto).CreateEncryptor(); break;
                    case SymmetricMode.DES: cryptoTransform = ((DESCryptoServiceProvider)this.Crypto).CreateEncryptor(); break;
                    case SymmetricMode.TripleDES: cryptoTransform = ((TripleDESCryptoServiceProvider)this.Crypto).CreateEncryptor(); break;
                }

                using (CryptoStream encStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
                {
                    UTF8Encoding oUTF8 = new UTF8Encoding(false);

                    using (StreamWriter streamWriter = new StreamWriter(encStream, oUTF8))
                    {
                        // Write the originalValue to the stream.
                        streamWriter.Write(originalString);
                        // Close the StreamWriter.
                        streamWriter.Close();
                    }
                    // Close the CryptoStream.
                    encStream.Close();
                }
                // Get an array of bytes that represents the memory stream.
                byResult = memoryStream.ToArray();
                // Close the memory stream.
                memoryStream.Close();
            }
            // Return the encrypted string.
            return byResult;
        }
        /// <summary>
        /// 依據設定的演算法進行資料加密。
        /// </summary>
        /// <param name="originalString">原始未加密的字串。</param>
        /// <param name="key">金鑰字串。</param>
        /// <param name="iv">向量字串。</param>
        /// <returns>計算出來的加密字串。</returns>
        public string Encrypt(string originalString, string key, string iv)
        {
            string szResult = String.Empty;
            // Encrypt a string to bytes.
            byte[] byBuffer = this.EncryptToBytes(originalString, key, iv);
            // Convert to base 64 string.
            szResult = Convert.ToBase64String(byBuffer);
            // Return the encrypted string.
            return szResult;
        }
        /// <summary>
        /// 依據設定的演算法進行資料解密。
        /// </summary>
        /// <param name="encryptedBytes">被加密的位元組。</param>
        /// <param name="key">金鑰字串。</param>
        /// <param name="iv">向量字串。</param>
        /// <returns>原始的字串。</returns>
        public string DecryptFromBytes(byte[] encryptedBytes, string key, string iv)
        {
            string szResult = String.Empty;
            // Set Key and IV.
            this.Crypto.Key = Encoding.UTF8.GetBytes(key);
            this.Crypto.IV = Encoding.UTF8.GetBytes((this.Mode == SymmetricMode.AES ? iv : iv.Substring(0, 8)));
            // Create a memory stream to the passed buffer.
            using (MemoryStream memoryStream = new MemoryStream(encryptedBytes))
            {
                ICryptoTransform cryptoTransform = null;

                switch (this.Mode)
                {
                    case SymmetricMode.AES: cryptoTransform = ((AesCryptoServiceProvider)this.Crypto).CreateDecryptor(); break;
                    case SymmetricMode.DES: cryptoTransform = ((DESCryptoServiceProvider)this.Crypto).CreateDecryptor(); break;
                    case SymmetricMode.TripleDES: cryptoTransform = ((TripleDESCryptoServiceProvider)this.Crypto).CreateDecryptor(); break;
                }

                // Create a CryptoStream using the memory stream and the CSP DES key. 
                using (CryptoStream encStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Read))
                {
                    UTF8Encoding oUTF8 = new UTF8Encoding(false);

                    // Create a StreamReader for reading the stream.
                    using (StreamReader streamReader = new StreamReader(encStream, oUTF8))
                    {
                        // Read the stream as a string.
                        szResult = streamReader.ReadToEnd();
                        // Close the streams.
                        streamReader.Close();
                    }
                    // Close the CryptoStream.
                    encStream.Close();
                }
                // Close the memory stream.
                memoryStream.Close();
            }
            // Get padding character.
            int nPadding = Convert.ToInt32(szResult[szResult.Length - 1]);
            // Remove padding character for PHP.
            if (nPadding.Equals(4))
            {
                szResult = szResult.Substring(0, szResult.Length - nPadding);
            }
            // Return the decrypted string.
            return szResult;
        }
        /// <summary>
        /// 依據設定的演算法進行資料解密。
        /// </summary>
        /// <param name="encryptedString">被加密的字串。</param>
        /// <param name="key">金鑰字串。</param>
        /// <param name="iv">向量字串。</param>
        /// <returns>原始的字串。</returns>
        public string Decrypt(string encryptedString, string key, string iv)
        {
            string szResult = String.Empty;
            byte[] byBuffer = null;
            // Convert to an array of bytes.
            byBuffer = Convert.FromBase64String(encryptedString);
            // Decrypted bytes to original string.
            szResult = this.DecryptFromBytes(byBuffer, key, iv);
            // Return the decrypted string.
            return szResult;
        }
        /// <summary>
        /// 將 System.Security.Cryptography.AsymmetricAlgorithm 類別目前的執行個體所使用的資源全部釋出。
        /// </summary>
        public void Dispose()
        {
            this.Crypto.Clear();
            this.Crypto.Dispose();
        }
    }
}

Java 範例 (TripleDES):
package sample;

import javax.crypto.*;
import javax.crypto.spec.*;
import javax.xml.bind.DatatypeConverter;

/**
 * TripleDES 加解密服務的類別。
 * @author andychao
 */
public class TripleDES {
    private Cipher oCipher = null;
    private SecretKeySpec specKey = null;
    private IvParameterSpec specIV = null;
    
    public TripleDES(String key, String iv) throws Exception {
        // Create Instance.
        this.oCipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        // Decode Key and IV.
        byte[] byKeys = key.getBytes("UTF-8");
        byte[] byIVs = iv.substring(0, 8).getBytes("UTF-8");
        // Duplicate Key the first 8 bytes of the key array.
        if (byKeys.length == 16) {
            byte[] byNewKeys = new byte[24];
            System.arraycopy(byKeys, 0, byNewKeys, 0, 16);
            System.arraycopy(byKeys, 0, byNewKeys, 16, 8);
            byKeys = byNewKeys;
        }
        // Restore Key and IV.
        this.specKey = new SecretKeySpec(byKeys, "DESede");
        this.specIV = new IvParameterSpec(byIVs);
    }
    
    public String Encrypt(String originalValue) throws Exception {
        // Declare variables.
        int nLength = 0;
        String szResult = null;
        // Initialize Cipher of TripleDES.
        this.oCipher.init(Cipher.ENCRYPT_MODE, this.specKey, this.specIV);
        // Convert original value to bytes.
        byte[] bySource = originalValue.getBytes("UTF-8");
        // Get output size for ciphers.
        byte[] byCiphers = new byte[this.oCipher.getOutputSize(bySource.length)];
        // Encrypt data.
        nLength = this.oCipher.update(bySource, 0, bySource.length, byCiphers, 0);
        nLength += this.oCipher.doFinal(byCiphers, nLength);
        // Convert to Base64 string.
        szResult = DatatypeConverter.printBase64Binary(byCiphers);
        // Return encrypted data.
        return szResult;
    }
    
    public String Decrypt(String encryptValue) throws Exception {
        // Declare variables.
        int nLength = 0;
        String szResult = null;
        // Decrypt string as bytes.
        byte[] byCiphers = DatatypeConverter.parseBase64Binary(encryptValue);
        // Initialize Cipher of TripleDES.
        this.oCipher.init(Cipher.DECRYPT_MODE, this.specKey, this.specIV);
        // Get output size for ciphers.
        byte[] bySource = new byte[this.oCipher.getOutputSize(byCiphers.length)];
        // Decrypt data.
        nLength = this.oCipher.update(byCiphers, 0, byCiphers.length, bySource, 0);
        nLength += this.oCipher.doFinal(bySource, nLength);
        // Convert bytes to string.
        szResult = new String(bySource, 0, bySource.length, "UTF-8");
        // Return Decrypted data.
        return szResult;
    }
}

Java 範例 (AES):
package sample;

import javax.crypto.*;
import javax.crypto.spec.*;
import javax.xml.bind.DatatypeConverter;
import java.security.*;

/**
 * AES 加解密服務的類別。
 * @author andychao
 */
public class AES {
    private Cipher oCipher = null;
    private SecretKeySpec specKey = null;
    private IvParameterSpec specIV = null;
    
    public AES(String key, String iv) throws Exception {
        // Create Instance.
        this.oCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // Decode Key and IV.
        byte[] byKeys = key.getBytes("UTF-8");
        byte[] byIVs = iv.getBytes("UTF-8");
        // Restore Key and IV.
        this.specKey = new SecretKeySpec(byKeys, "AES");
        this.specIV = new IvParameterSpec(byIVs);
    }
    
    public String Encrypt(String originalValue) throws Exception {
        // Declare variables.
        int nLength = 0;
        String szResult = null;
        // Initialize Cipher of AES.
        this.oCipher.init(Cipher.ENCRYPT_MODE, this.specKey, this.specIV);
        // Convert original value to bytes.
        byte[] bySource = originalValue.getBytes("UTF-8");
        // Get output size for ciphers.
        byte[] byCiphers = new byte[this.oCipher.getOutputSize(bySource.length)];
        // Encrypt data.
        nLength = this.oCipher.update(bySource, 0, bySource.length, byCiphers, 0);
        nLength += this.oCipher.doFinal(byCiphers, nLength);
        // Convert to Base64 string.
        szResult = DatatypeConverter.printBase64Binary(byCiphers);
        // Return encrypted data.
        return szResult;
    }
    
    public String Decrypt(String encryptValue) throws Exception {
        // Declare variables.
        int nLength = 0;
        String szResult = null;
        // Decrypt string as bytes.
        byte[] byCiphers = DatatypeConverter.parseBase64Binary(encryptValue);
        // Initialize Cipher of AES.
        this.oCipher.init(Cipher.DECRYPT_MODE, this.specKey, this.specIV);
        // Get output size for ciphers.
        byte[] bySource = new byte[this.oCipher.getOutputSize(byCiphers.length)];
        // Decrypt data.
        nLength = this.oCipher.update(byCiphers, 0, byCiphers.length, bySource, 0);
        nLength += this.oCipher.doFinal(bySource, nLength);
        // Convert bytes to string.
        szResult = new String(bySource, 0, bySource.length, "UTF-8");
        // Return Decrypted data.
        return szResult;
    }
}

PHP 範例 (TripleDES):
/**
 * TripleDES 加解密服務的類別。
 */
class TripleDES {

    private $Key = '1234567890';
    private $IV = '1234567890';
    /**
     * TripleDES 加解密服務類別的建構式。
     */
    function __construct($key, $iv) {
        $this->TripleDES($key, $iv);
    }
    /**
     * TripleDES 加解密服務類別的實體。
     */
    function TripleDES($key, $iv) {
        $this->Key = substr($key . $key, 0, 24);
        $this->IV = substr($iv, 0, 8);
    }
    /**
     * 加密服務的方法。
     */
    function Encrypt($data)
    {
        $szBlockSize = mcrypt_get_block_size(MCRYPT_TRIPLEDES, MCRYPT_MODE_CBC);
        $szPad = $szBlockSize - (strlen($data) % $szBlockSize);
        $szData = mcrypt_encrypt(MCRYPT_TRIPLEDES, $this->Key, $data. str_repeat(chr($szPad), $szPad), MCRYPT_MODE_CBC, $this->IV);
        $szData = base64_encode($szData);

        return $szData;
    }
    /**
     * 解密服務的方法。
     */
    function Decrypt($data)
    {
        $szValue = base64_decode($data);
        $szValue = mcrypt_decrypt(MCRYPT_TRIPLEDES, $this->Key, $szValue, MCRYPT_MODE_CBC, $this->IV);
        $nLength = strlen($szValue);
        $nPadding = ord($szValue[$nLength - 1]);
        $szValue = substr($szValue, 0, $nLength - $nPadding);

        return $szValue;
    }
}

PHP 範例 (AES):
/**
 * AES 加解密服務的類別。
 */
class AES {

    private $Key = '1234567890';
    private $IV = '1234567890';
    /**
     * AES 加解密服務類別的建構式。
     */
    function __construct($key, $iv) {
        $this->AES($key, $iv);
    }
    /**
     * AES 加解密服務類別的實體。
     */
    function AES($key, $iv) {
        $this->Key = $key;
        $this->IV = $iv;
    }
    /**
     * 加密服務的方法。
     */
    function Encrypt($data)
    {
        $szBlockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $szPad = $szBlockSize - (strlen($data) % $szBlockSize);
        $szData = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->Key, $data. str_repeat(chr($szPad), $szPad), MCRYPT_MODE_CBC, $this->IV);
        $szData = base64_encode($szData);

        return $szData;
    }
    /**
     * 解密服務的方法。
     */
    function Decrypt($data)
    {
        $szValue = base64_decode($data);
        $szValue = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->Key, $szValue, MCRYPT_MODE_CBC, $this->IV);
        $nLength = strlen($szValue);
        $nPadding = ord($szValue[$nLength - 1]);
        $szValue = substr($szValue, 0, $nLength - $nPadding);

        return $szValue;
    }
}

Delphi 範例 (AES Ref. kuraki5336 網誌):
{ TODO: xStr 明文, xKey 加密金鑰 xIV 加密向量 }
function EncryAES(xStr, xKey, xIV: AnsiString): String;
var
    Source: TStringStream;
    Dest: TStringStream;
    { TODO: 引用 ELAES.PAS 就會認識了 }
    mKey: TAESKey128;
    mIV: TAESBuffer;
    strRemainder, i: Integer;
    mstr, aKey, aIV: Ansistring;
begin
    try
        { TODO: 不要覺得奇怪,不這樣重給,值就是會不對  } 
        { TODO: ※我外部傳入其實已經是ANSISTING了不知道為什麼直接使用上面的xStr xKey 這載入的值就會有異常。因此從新給一次※ } 
        mstr := xStr;
        aKey := xKey;
        aIV :=xIV;
        { TODO: pksc5  PHP 是用pksc5 但是DELPHI & JAVA 就是要用pksc5才會對  }
        { TODO: ※補位的主要原因是在   EncryptAESStreamCBC 會檢核你輸入的數值是否有達到128K 192K 248K 因此來源值需要補位※ } 
        strRemainder :=Length(mstr) mod 16;
        strRemainder := 16 - strRemainder;
        for i := 1 to strRemainder do
        begin
            mstr := mstr + Chr(strRemainder);
        end;
        { TODO: KEY & IV 將KEY轉成Array of Byte }
        FillChar( mKey, SizeOf(mKey), #0 );
        Move( PAnsiChar(aKey)^, mKey, 16);
        FillChar( mIV, SizeOf(mIV), #0 );
        Move( PAnsiChar(aIV)^, mIV, 16);
        { TODO: Input & output 並賦予值 }
        Source := TStringStream.Create(mstr, 65001);
        Dest   := TStringStream.Create('', 65001);
        { TODO: 加密 }
        EncryptAESStreamCBC( Source, 0, mKey, mIV, Dest );
        { TODO: BASE64 千萬不要對Dest做任何的變化,保持BYTE來做加解密}
        Result := EncodeBase64(Dest.Bytes, Dest.size);
    finally
        Source.Free;
        Dest.Free;
    end;
end;

{ TODO: 再回傳上有一組資訊是被加密過得因此我們必須解密才能得到裡面的值做紀錄 }
{ TODO: xStr的值會是ALLPAY回應給我們的加密電文 , 因此要按照當初加密的方式反向解密 }
function DecryptAES(xStr, xKey, xIV: AnsiString): String;
var
    Source: TStringStream;
    Dest: TStringStream;
    mKey: TAESKey128;
    mIV: TAESBuffer;
    strRemainder,i:Integer;
    mstr,aKey,aIV:Ansistring;
    mBytes, mReturnByte :TBytes;
begin
    try
        { TODO: Input & output }
        Dest := TStringStream.Create('', 65001);
        Source := TStringStream.Create('', 65001);
        { TODO: 不要覺得奇怪,不這樣重給,值就是會不對 } 
        mstr := xStr;
        aKey := xKey;
        aIV := xIV;
        { TODO: 反解密.DecodeBase64 }
        mBytes := DecodeBase64(mstr);
        { TODO: 將資料已Byte方式輸入...千千萬萬不要.....拿字串輸入哦 }
        { TODO: 不可以這樣寫 Source.WriteBuffer(mBytes, mBytes.size); 寫進去的值根本不對。我不知道為什麼@@ }
        Source.Position := 0;
        for i := 0 to length(mBytes) -1 do
        begin
            Source.WriteBuffer(mBytes[i], 1);
        end;
        { TODO: 不用補位 }
        { TODO: KEY & IV 將KEY轉成Array of Byte }
        FillChar( mKey, SizeOf(mKey), #0 );
        Move( PAnsiChar(aKey)^, mKey, 16);
        FillChar( mIV, SizeOf(mIV), #0 );
        Move( PAnsiChar(aIV)^, mIV, 16);
        { TODO: 解密 }
        DecryptAESStreamCBC( Source, 0, mKey, mIV, Dest );
        { TODO: 濾掉byte < 32 --小於32的都是特殊字元對於解析無意義}
        Dest.Position := 0;
        SetLength(mReturnByte, Dest.size);
        for i := 0 to Dest.size do
        begin
           if (Dest.Bytes[i] <= 32) then
              continue;
           Dest.Read(mReturnByte[i], 1);
        end;
        { TODO: 轉字串 }
        Result := Dest.DataString;
    finally
        Source.Free;
        Dest.Free;
    end;
end;

以上的範例,

眼尖的朋友會發現,

為什麼其他語言都是 PKCS5,

而 .NET 是使用 PKCS7,

但是加解密是可以互通的呢?!

原因是 PKCS5 與 PKCS7 裡使用的填補演算法是相同~~


就醬~打完收工~~


沒有留言: