AES對稱加解密

簡介

高級加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣爲全世界所使用。通過五年的甄選流程,高級加密標準由美國國家標準與技術研究院(NIST)於2001年11月26日發佈於FIPS PUB 197,並在2002年5月26日成爲有效的標準。2006年,高級加密標準已然成爲對稱密鑰加密中最流行的算法之一。
不一樣於它的前任標準DES,Rijndael使用的是代換-置換網絡,而非Feistel架構。AES在軟件及硬件上都能快速地加解密,相對來講較易於實做,且只須要不多的存儲器。做爲一個新的加密標準,目前正被部署應用到更廣大的範圍。php

設計思想

Rijndael密碼的設計力求知足如下3條標準:
① 抵抗全部已知的攻擊。
② 在多個平臺上速度快,編碼緊湊。
③ 設計簡單。
當前的大多數分組密碼,其輪函數是Feistel結構。
Rijndael沒有這種結構。
Rijndael輪函數是由3個不一樣的可逆均勻變換java

加密模式

算法/模式/填充                16字節加密後數據長度        不滿16字節加密後長度   
AES/CBC/NoPadding             16                          不支持   
AES/CBC/PKCS5Padding          32                          16   
AES/CBC/ISO10126Padding       32                          16   
AES/CFB/NoPadding             16                          原始數據長度   
AES/CFB/PKCS5Padding          32                          16   
AES/CFB/ISO10126Padding       32                          16   
AES/ECB/NoPadding             16                          不支持   
AES/ECB/PKCS5Padding          32                          16   
AES/ECB/ISO10126Padding       32                          16   
AES/OFB/NoPadding             16                          原始數據長度   
AES/OFB/PKCS5Padding          32                          16   
AES/OFB/ISO10126Padding       32                          16   
AES/PCBC/NoPadding            16                          不支持   
AES/PCBC/PKCS5Padding         32                          16   
AES/PCBC/ISO10126Padding      32                          16

ECB模式(電子密碼本模式:Electronic codebook)

ECB是最簡單的塊密碼加密模式,加密前根據加密塊大小(如AES爲128位)分紅若干塊,以後將每塊使用相同的密鑰單獨加密,解密同理。
android

ECB加密流程(圖片來自維基百科)
算法

ECB模式因爲每塊數據的加密是獨立的所以加密和解密均可以並行計算,ECB模式最大的缺點是相同的明文塊會被加密成相同的密文塊,這種方法在某些環境下不能提供嚴格的數據保密性。數組

CBC模式(密碼分組連接:Cipher-block chaining)

CBC模式對於每一個待加密的密碼塊在加密前會先與前一個密碼塊的密文異或而後再用加密器加密。第一個明文塊與一個叫初始化向量的數據塊異或。
網絡

CBC加密流程(圖片來自維基百科)
架構

AES_cbc_encrypt容許length不是16(128位)的整數倍,不足的部分會用0填充,輸出老是16的整數倍。完成加密或解密後會更新初始化向量IV。app

CBC模式相比ECB有更高的保密性,但因爲對每一個數據塊的加密依賴與前一個數據塊的加密因此加密沒法並行。與ECB同樣在加密前須要對數據進行填充,不是很適合對流數據進行加密。函數

CFB模式(密文反饋:Cipher feedback)

與ECB和CBC模式只可以加密塊數據不一樣,CFB可以將塊密文(Block Cipher)轉換爲流密文(Stream Cipher)。
編碼

CFB加密流程(圖片來自維基百科)

注意:CFB、OFB和CTR模式中解密也都是用的加密器而非解密器。
CFB的加密工做分爲兩部分:

  • 將一前段加密獲得的密文再加密;
  • 將第1步加密獲得的數據與當前段的明文異或。

因爲加密流程和解密流程中被塊加密器加密的數據是前一段密文,所以即便明文數據的長度不是加密塊大小的整數倍也是不須要填充的,這保證了數據長度在加密先後是相同的。

這種模式稱爲128位的CFB模式(又稱CFB128)在OpenSSL中用來進行這種加解密的函數爲AES_cfb128_encrypt
CFB128是每處理128位數據調用一次加密器,此外還有兩種經常使用的CFB是CFB8和CFB1,前者每處理8位調用一次加密器,後者每處理1位調用1次加密器,就運算量來說CFB1是CFB8的8倍,是CFB128的128倍。對於CFB8和CFB1須要將IV做爲移位寄存器。

CFB8的加密流程

  1. 使用加密器加密IV的數據;
  2. 將明文的最高8位與IV的最高8位異或獲得8位密文;
  3. 將IV數據左移8位,最低8位用剛剛計算獲得的8位密文補上。

重複1到3。

CFB1的加密流程

  1. 使用加密器加密IV的數據;
  2. 將明文的最高1位與IV的最高1位異或獲得1位密文;
  3. 將IV數據左移1位,最低1位用剛剛計算獲得的1位密文補上。

重複1到3。

CFB模式很是適合對流數據進行加密,解密能夠並行計算。

OFB模式(輸出反饋:Output feedback)

OFB是先用塊加密器生成密鑰流(Keystream),而後再將密鑰流與明文流異或獲得密文流,解密是先用塊加密器生成密鑰流,再將密鑰流與密文流異或獲得明文,因爲異或操做的對稱性因此加密和解密的流程是徹底同樣的。

OFB加密流程

OFB與CFB同樣都很是適合對流數據的加密,OFB因爲加密和解密都依賴與前一段數據,因此加密和解密都不能並行。

主要參數

模式、填充偏移量iv

注意

PHP的AES加密填充只有ZeroPadding(補零 - 由於數據長度不是16的整數倍就須要填充),因此跨語言的普通加密需求就咱們就採用了AES/ECB/ZeroPadding(ECB模式無IV向量),而JAVA沒有ZeroPadding,因此只能模擬實現

JAVA實現AES/ECB/ZeroPadding

import android.util.Base64;

import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Created by lixin(178078114@qq.com) on 2016/12/9.
 */

public class AESCryptoUtils {
    /**
     * 算法/模式/填充
     **/
    private static final String CipherMode = "AES/ECB/NoPadding";


    /**
     * 建立密鑰
     **/
    private static SecretKeySpec createKey(String key) {
        byte[] data = null;
        if (key == null) {
            key = "";
        }
        StringBuffer sb = new StringBuffer(16);
        sb.append(key);
        while (sb.length() < 16) {
            sb.append("0");
        }
        if (sb.length() > 16) {
            sb.setLength(16);
        }


        try {
            data = sb.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return new SecretKeySpec(data, "AES");
    }


    private static IvParameterSpec createIV(String password) {
        byte[] data = null;
        if (password == null) {
            password = "";
        }
        StringBuffer sb = new StringBuffer(16);
        sb.append(password);
        while (sb.length() < 16) {
            sb.append("0");
        }
        if (sb.length() > 16) {
            sb.setLength(16);
        }


        try {
            data = sb.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return new IvParameterSpec(data);
    }

    /**
     * 加密字節數據
     **/
    public static byte[] encrypt(byte[] content, String password) {//}, String iv) {
        try {
            Cipher cipher = Cipher.getInstance(CipherMode);
            int blockSize = cipher.getBlockSize();
            byte[] dataBytes = content;
            int plaintextLength = dataBytes.length;
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }
            //字節數組默認爲0,不用手動補了
            byte[] plaintext = new byte[plaintextLength];
            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
            SecretKeySpec key = new SecretKeySpec(password.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);//, createIV(iv));
            byte[] result = cipher.doFinal(plaintext);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 加密(結果爲16進制字符串)
     **/
    public static String encrypt(String content, String password) {//, String iv) {
        byte[] data = null;
        try {
            data = content.getBytes("UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        data = encrypt(data, password);//, iv);
        String result = Base64.encodeToString(data, Base64.DEFAULT);
        return result;
    }


    /**
     * 解密字節數組
     **/
    public static byte[] decrypt(byte[] content, String password, String iv) {
        try {
            SecretKeySpec key = createKey(password);
            Cipher cipher = Cipher.getInstance(CipherMode);
            cipher.init(Cipher.DECRYPT_MODE, key, createIV(iv));
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 解密(輸出結果爲字符串)
     **/
    public static String decrypt(String content, String password, String iv) {
        byte[] data = null;
        try {
            data = hex2byte(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        data = decrypt(data, password, iv);
        if (data == null)
            return null;
        String result = null;
        try {
            result = new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 字節數組轉成16進制字符串
     **/
    public static String byte2hex(byte[] b) { // 一個字節的數,
        StringBuffer sb = new StringBuffer(b.length * 2);
        String tmp = "";
        for (int n = 0; n < b.length; n++) {
            // 整數轉成十六進制表示
            tmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
            if (tmp.length() == 1) {
                sb.append("0");
            }
            sb.append(tmp);
        }
        return sb.toString().toUpperCase(); // 轉成大寫
    }


    /**
     * 將hex字符串轉換成字節數組
     **/
    private static byte[] hex2byte(String inputString) {
        if (inputString == null || inputString.length() < 2) {
            return new byte[0];
        }
        inputString = inputString.toLowerCase();
        int l = inputString.length() / 2;
        byte[] result = new byte[l];
        for (int i = 0; i < l; ++i) {
            String tmp = inputString.substring(2 * i, 2 * i + 2);
            result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
        }
        return result;
    }
}

PHP實現AES/ECB/ZeroPadding

php默認狀況只有ZeroPadding模式

/**
     * 加密
     *
     * @param string $key
     *          密鑰
     * @param string $str
     *          需加密的字符串
     * @return type
     */
    private function encrypt($key, $str) {
        return mcrypt_encrypt ( MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
    }

    /**
     * 解密
     *
     * @param type $key
     * @param type $str
     * @return type
     */
    private function decrypt($key, $str) {
        $data=base64_decode($str);
        return mcrypt_decrypt ( MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB);
    }

.NET實現AES/ECB/ZeroPadding

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Leestar
{
    /// <summary> 
    /// AESEncrypt加密解密算法。 
    /// </summary> 
    public sealed class AESEncrypt
    {
        public static string Encrypt(string toEncrypt)
        {
            byte[] keyArray = UTF8Encoding.UTF8.GetBytes("這裏是密匙");
            byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.Zeros;
            ICryptoTransform cTransform = rDel.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        public static string Decrypt(string toDecrypt)
        {
            byte[] keyArray = UTF8Encoding.UTF8.GetBytes("這裏是密匙");
            byte[] toEncryptArray = Convert.FromBase64String(toDecrypt);

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.None;

            ICryptoTransform cTransform = rDel.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            return UTF8Encoding.UTF8.GetString(resultArray);
        }

    }
}

爲啥要base64

X.509公鑰證書也好,電子郵件數據也好,常常要用到Base64編碼,那麼爲何要做一下這樣的編碼呢?咱們知道在計算機中任何數據都是按ascii碼存儲的,而ascii碼的128~255之間的值是不可見字符。而在網絡上交換數據時,好比說從A地傳到B地,每每要通過多個路由設備,因爲不一樣的設備對字符的處理方式有一些不一樣,這樣那些不可見字符就有可能被處理錯誤,這是不利於傳輸的。因此就先把數據先作一個Base64編碼,通通變成可見字符,這樣出錯的可能性就大下降了。

參考:
http://php.net/manual/en/function.mcrypt-encrypt.php
https://blog.poxiao.me/p/advanced-encryption-standard-and-block-cipher-mode/
https://my.oschina.net/Jacker/blog/86383

相關文章
相關標籤/搜索