在網路上找到許多不同語言的實作方式,但加密出來的結果都不一樣
原來是 AES encryption 有以下幾種mode
● ECB should not be used if encrypting more than one block of data with the same key.
當使用相同key加密一個block以上的資料時,ECB不應該被使用
● CBC, OFB and CFB are similar, however OFB/CFB is better because you only need encryption and not decryption, which can save code space.
CBC, OFB 和CFB類似。OFB/CFB比較好,因為你只需要加密不需要解密,以減少code的量
● CTR is used if you want good parallelization (ie. speed), instead of CBC/OFB/CFB.
當你想要好的平行處理(如:速度),使用CTR。而不是CBC/OFB/CFB。
The most important caveat with CTR mode is that you never, ever re-use the same counter value with the same key. If you do so, you have effectively given away your plaintext. ( http://stackoverflow.com/questions/4951468/ctr-mode-use-of-initial-vectoriv CTR mode use of Initial Vector(IV) )
最重要的是,你不要重複使用相同的key(IV,隨機產生),如果你這樣做,你已有效地給了你的明文
● XTS mode is the most common if you are encoding a random accessible data (like a hard disk or RAM).
XTS用在硬碟和RAM上
● OCB is by far the best mode, as it allows encryption and authentication in a single pass. However there are patents on it in USA.
OCB最好,因為他允許加密和認證在單通道。然而美國擁有其專利。( 所以意味著你在網路上找不到實作他的code )
你必須每次都用獨特的IV去加密,如果你不能保證他的隨機性,請用只需要隨機數(非IV)的OCB。固定的IV使得人們能不斷的猜測下一個,隨機數能避免這個風險
初始向量 Initialization vector (IV) 可被公開
http://ijecorp.blogspot.com/2013/08/python-m2crypto-aes-encrypt-decrypt.html
IV 本身並不需要保護,它是可以被公開的。而IV的最大長度必須是 16 bytes,而且產生IV的方式必須是無法預測的,也就是隨機產生即可。
http://stackoverflow.com/questions/8804574/aes-encryption-how-to-transport-iv
There is no security hole by sending the IV in clear text - this is similar to storing the salt for a hash in clear: As long as the attacker has no controll over the IV/salt, and as long as it is random, there is nor problem.
用明文傳送IV沒有安全的漏洞。這就像你做hash加了salt一樣,只要攻擊者無法掌握IV(salt)並且他是隨機的,就不會有問題。
使用php做AES CBC 128 pkcs5padding加密
結果:
e5bca0e6a0b9 //這邊是utf8中文字轉hex的結果,如果其他地方字串轉hex不是這個代表他們的字串原本編碼不是utf-8
$sStr:张根 // pkcs5 padding的結果
$encrypted:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果
\getDecrypt($encrypted, $key):张根 // aes解密結果
說明:
因為java那邊只能用 128bit,所以只能選 MCRYPT_RIJNDAEL_128
$iv = Initial Vector(IV) 初始向量
在 https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 範例中,我們可以看到他加密不是用 mcrypt_encrypt 而是
mcrypt_generic() => 低階API ,更有彈性
mcrypt_encrypt() => 高階工具( higher-level utility )
參考資料:
http://stackoverflow.com/questions/2773535/mcrypt-generic-vs-mcrypt-encrypt mcrypt_generic vs mcrypt_encrypt
http://php.net/manual/en/function.mcrypt-encrypt.php
string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )
mode的格式是 MCRYPT_MODE_modename ,modename可使用"ecb", "cbc", "cfb", "ofb", "nofb" or "stream"
如: MCRYPT_MODE_ECB
因為mcrypt_encrypt() 出來的結果打印是二進位亂碼,所以都用 bin2hex()或base64_encode()去轉換一次
報錯:
Warning: mcrypt_encrypt(): Key of size 15 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported
如果你出現這個錯誤,請把$key補到16位(或24, 32位)
$key=$key."\0"; //缺幾位就補幾個
PHP範例參考資料:
https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 使用pkcs5_pad()方法
http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php getEncrypt($sStr, $sKey)和getDecrypt($sStr, $sKey) 原型
使用Java做AES CBC 128 pkcs5padding加密
結果:
encrypted string:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果,需與php結果一致
张根 // aes 解密結果
e5bca0e6a0b9 // "张根"utf8轉hex的結果
說明:
parseHexStr2Byte(String hexStr) 和 parseByte2HexStr(byte buf[]) 這兩個function在這邊純粹測試用,與加解密無關。可忽略
我遇到的最大難點之一就是,java和php 英文字加密出來結果一樣,但是中文加密結果不一樣。( 這邊23樓也遇到一樣問題: http://my.oschina.net/Jacker/blog/86383?p=3#comments )
原因是java在加密前的中文編碼有問題。先用
System.out.println(parseByte2HexStr("张根".getBytes("utf-8")));
檢測hex是否相同
加密時在外面就轉成utf-8再加密
encrypt(key1, key2, new String("张根".getBytes("utf-8")))
如果你的cipher(密文),想要用No Padding,如
Cipher cipher = Cipher.getInstance("AES/CBC/NoPADDING");
這樣你要加密的明文( "张根" ) 必須改成16位的字,否則java會報錯
encrypt(key1, key2, new String("123456789012345".getBytes("utf-8"))) // 必須改成16位字串,如1234567890123456
報錯:
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
雖然php不做padding可以加密,但結果不一樣。為了配合java這邊,php那邊還是要做pkcs5 padding
原因:
java文件檔的編碼不是utf-8
解法:
eclipse => "左邊導航欄"你的"專案"上點右鍵 => Properties => Resource => Text file encoding 選
Other: UTF-8 ( 不要選Inherited from container (GBK) ) => OK ( 這樣你原本GBK的文件中文內容會變成亂碼,代表原本編碼錯誤 )
JAVA範例參考資料:
http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example Simple java AES encrypt/decrypt example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | $value = "张根" ; $key = "Bar12345Bar12345" ; //16 Character Key echo strToHex( '张根' ); // hex: e5bca0e6a0b9 $encrypted = getEncrypt( $value , $key ); echo "\n\$encrypted:" . $encrypted ; echo "\n\getDecrypt(\$encrypted, \$key):" .getDecrypt( $encrypted , $key ); function pkcs5_pad ( $text , $blocksize ) { // https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php $pad = $blocksize - ( strlen ( $text ) % $blocksize ); return $text . str_repeat ( chr ( $pad ), $pad ); } function getEncrypt( $sStr , $sKey ) { // http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php global $iv ; $sStr = pkcs5_pad( $sStr , 16); // 這個16是 mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) 的結果 echo "\n\$sStr:" . $sStr ; // 測試pkcs5 padding的結果 // 產生$iv,如果用class寫,可以避免全域變數 // $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND); // echo "\n\$iv:".$iv; // 這是隨機產生的內容 return base64_encode ( // 用bin2hex()亦可,但解密時要用hex2bin() mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $sKey , $sStr , MCRYPT_MODE_CBC, "ThisIsASecretKet" // $iv,測試時寫死 ) ); } function getDecrypt( $sStr , $sKey ) { global $iv ; // 要與 getEncrypt()產生的$iv一致,才能解出來 return mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $sKey , base64_decode ( $sStr ), // 加密時用bin2hex(),則解密時要用hex2bin() MCRYPT_MODE_CBC, "ThisIsASecretKet" ); } function strToHex( $string ) // http://ditio.net/2008/11/04/php-string-to-hex-and-hex-to-string-functions/ { $hex = '' ; for ( $i =0; $i < strlen ( $string ); $i ++) { $hex .= dechex (ord( $string [ $i ])); } return $hex ; } |
結果:
e5bca0e6a0b9 //這邊是utf8中文字轉hex的結果,如果其他地方字串轉hex不是這個代表他們的字串原本編碼不是utf-8
$sStr:张根 // pkcs5 padding的結果
$encrypted:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果
\getDecrypt($encrypted, $key):张根 // aes解密結果
說明:
因為java那邊只能用 128bit,所以只能選 MCRYPT_RIJNDAEL_128
$iv = Initial Vector(IV) 初始向量
在 https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 範例中,我們可以看到他加密不是用 mcrypt_encrypt 而是
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static function encrypt( $input , $key ) { $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB); $input = Security::pkcs5_pad( $input , $size ); $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '' , MCRYPT_MODE_ECB, '' ); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size( $td ), MCRYPT_RAND); mcrypt_generic_init( $td , $key , $iv ); $data = mcrypt_generic( $td , $input ); mcrypt_generic_deinit( $td ); mcrypt_module_close( $td ); $data = base64_encode ( $data ); return $data ; }<span style= "font-family: Times New Roman;" ><span style= "white-space: normal;" > </span></span> |
mcrypt_encrypt() => 高階工具( higher-level utility )
參考資料:
http://stackoverflow.com/questions/2773535/mcrypt-generic-vs-mcrypt-encrypt mcrypt_generic vs mcrypt_encrypt
http://php.net/manual/en/function.mcrypt-encrypt.php
string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )
mode的格式是 MCRYPT_MODE_modename ,modename可使用"ecb", "cbc", "cfb", "ofb", "nofb" or "stream"
如: MCRYPT_MODE_ECB
因為mcrypt_encrypt() 出來的結果打印是二進位亂碼,所以都用 bin2hex()或base64_encode()去轉換一次
報錯:
Warning: mcrypt_encrypt(): Key of size 15 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported
如果你出現這個錯誤,請把$key補到16位(或24, 32位)
$key=$key."\0"; //缺幾位就補幾個
PHP範例參考資料:
https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 使用pkcs5_pad()方法
http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php getEncrypt($sStr, $sKey)和getDecrypt($sStr, $sKey) 原型
使用Java做AES CBC 128 pkcs5padding加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import java.io.UnsupportedEncodingException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class Encryptor { public static String encrypt(String key1, String key2, String value) { try { IvParameterSpec iv = new IvParameterSpec(key2.getBytes( "UTF-8" )); SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes( "UTF-8" ), "AES" ); Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5PADDING" ); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte [] encrypted = cipher.doFinal(value.getBytes()); System.out.println( "encrypted string:" + Base64.encodeBase64String(encrypted)); return Base64.encodeBase64String(encrypted); } catch (Exception ex) { ex.printStackTrace(); } return null ; } public static String decrypt(String key1, String key2, String encrypted) { try { IvParameterSpec iv = new IvParameterSpec(key2.getBytes( "UTF-8" )); SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes( "UTF-8" ), "AES" ); Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5PADDING" ); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte [] original = cipher.doFinal(Base64.decodeBase64(encrypted)); return new String(original); } catch (Exception ex) { ex.printStackTrace(); } return null ; } public static void main(String[] args) throws UnsupportedEncodingException { String key1 = "Bar12345Bar12345" ; // 128 bit key String key2 = "ThisIsASecretKet" ; System.out.println(decrypt(key1, key2, encrypt(key1, key2, new String( "张根" .getBytes( "utf-8" ))))); System.out.println(parseByte2HexStr( "张根" .getBytes( "utf-8" ))); // print "张根" utf-8 in hex } /** * 将16进制转换为二进制 * * @param hexStr * @return */ public static byte [] parseHexStr2Byte(String hexStr) { if (hexStr.length() < 1 ) return null ; byte [] result = new byte [hexStr.length() / 2 ]; for ( int i = 0 ; i < hexStr.length() / 2 ; i++) { int high = Integer.parseInt(hexStr.substring(i * 2 , i * 2 + 1 ), 16 ); int low = Integer.parseInt(hexStr.substring(i * 2 + 1 , i * 2 + 2 ), 16 ); result[i] = ( byte ) (high * 16 + low); } return result; } /** * 将二进制转换成16进制 * * @param buf * @return */ public static String parseByte2HexStr( byte buf[]) { StringBuffer sb = new StringBuffer(); for ( int i = 0 ; i < buf.length; i++) { String hex = Integer.toHexString(buf[i] & 0xFF ); if (hex.length() == 1 ) { hex = '0' + hex; } sb.append(hex.toLowerCase()); } return sb.toString(); } } |
結果:
encrypted string:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果,需與php結果一致
张根 // aes 解密結果
e5bca0e6a0b9 // "张根"utf8轉hex的結果
說明:
parseHexStr2Byte(String hexStr) 和 parseByte2HexStr(byte buf[]) 這兩個function在這邊純粹測試用,與加解密無關。可忽略
我遇到的最大難點之一就是,java和php 英文字加密出來結果一樣,但是中文加密結果不一樣。( 這邊23樓也遇到一樣問題: http://my.oschina.net/Jacker/blog/86383?p=3#comments )
原因是java在加密前的中文編碼有問題。先用
System.out.println(parseByte2HexStr("张根".getBytes("utf-8")));
檢測hex是否相同
加密時在外面就轉成utf-8再加密
encrypt(key1, key2, new String("张根".getBytes("utf-8")))
如果你的cipher(密文),想要用No Padding,如
Cipher cipher = Cipher.getInstance("AES/CBC/NoPADDING");
這樣你要加密的明文( "张根" ) 必須改成16位的字,否則java會報錯
encrypt(key1, key2, new String("123456789012345".getBytes("utf-8"))) // 必須改成16位字串,如1234567890123456
報錯:
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
雖然php不做padding可以加密,但結果不一樣。為了配合java這邊,php那邊還是要做pkcs5 padding
原因:
java文件檔的編碼不是utf-8
解法:
eclipse => "左邊導航欄"你的"專案"上點右鍵 => Properties => Resource => Text file encoding 選
Other: UTF-8 ( 不要選Inherited from container (GBK) ) => OK ( 這樣你原本GBK的文件中文內容會變成亂碼,代表原本編碼錯誤 )
JAVA範例參考資料:
http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example Simple java AES encrypt/decrypt example
使用javascript ( SlowAES )做AES CBC 128 pkcs5padding加密
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="aes">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>aes</title>
<script src="/jquery-1.10.2.min.js"></script>
<script src="../js/aes.js"></script>
<script src="../js/cryptoHelpers.js"></script>
<script src="../js/jsHash.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
/**
* An encryption setup to match our server-side one; see there for
* documentation on it.
**/
function decrypt(input, key){
var originalSize = 6;
// var iv = 'R1We4y0JRP5w06Z8tUBPAw==';
// iv = cryptoHelpers.base64.decode(iv); // 解密時需把base64加密後的iv解密成byte array
var iv = "ThisIsASecretKet";
iv = cryptoHelpers.convertStringToByteArray(iv);
var cipherIn = input;
// Set up encryption parameters
var keyAsNumbers = cryptoHelpers.toNumbers( bin2hex( key ) );
cipherIn = cryptoHelpers.base64.decode(cipherIn);
var decrypted = slowAES.decrypt(
cipherIn,
slowAES.modeOfOperation.CBC,
keyAsNumbers,
iv
);
return cryptoHelpers.decode_utf8(cryptoHelpers.convertByteArrayToString(decrypted));
}
function encrypt( plaintext, key ){
// Set up encryption parameters
plaintext = cryptoHelpers.encode_utf8(plaintext);
var inputData = cryptoHelpers.convertStringToByteArray(plaintext);
var keyAsNumber = cryptoHelpers.toNumbers(bin2hex(key));
var iv = cryptoHelpers.generateSharedKey(8); // 假設自動生成的iv做base64 encode加密後的結果是 R1We4y0JRP5w06Z8tUBPAw==
var iv = "ThisIsASecretKet";
iv = cryptoHelpers.convertStringToByteArray(iv);
var encrypted = slowAES.encrypt(
inputData,
slowAES.modeOfOperation.CBC,
keyAsNumber,
iv
);
return cryptoHelpers.base64.encode(encrypted);
}
// Equivilent to PHP bin2hex
function bin2hex (s) {
var i, f = 0,
a = [];
s += '';
f = s.length;
for (i = 0; i < f; i++) {
a[i] = s.charCodeAt(i).toString(16).replace(/^([\da-f])$/, "0$1");
}
return a.join('');
}
// Equivilent to PHP hex2bin
function hex2bin(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
/**
* Some simple testing code
**/
$(function(){
var key = "Bar12345Bar12345"; // key
var plaintext = "张根";
var output = "";
var cipherText = encrypt(plaintext,key);
var newPlaintext = decrypt(cipherText,key);
output += ("<br>plaintext=" + plaintext);
output += ("<br>cipherText=" + cipherText);
output += ("<br>newPlaintext=" + newPlaintext);
$('#output').html(output);
});
</script>
</body>
</html>
結果:
plaintext=张根
cipherText=LE/jvtjPWJk7qJc49Xl3eQ==
newPlaintext=张根
如果要傳做過base64加密後的iv給php端,php端的iv要這樣設定,才能解密
$aes->set_iv(base64_decode($iv));
SlowAES的aes.js、cryptoHelpers.js、jsHash.js
https://code.google.com/p/slowaes/source/browse/trunk/js/
加密出來的結果要傳送
POST
1. 塞入表單後submit POST
GET
1. 塞入表單後submit GET
2. 組URL
url = $('#action').val()+"&aes_encrypt="+encodeURIComponent($('#reqParam').val())+"&iv="+$('#iv').val();
location.href = url;
必須用在字段上使用 encodeURIComponent 。
1. 勿組出url後再encodeURIComponent(url), 因為http:// 也會被encode
2. 使用encodeURI無效
其他java或php實作AES範例:
http://www.movable-type.co.uk/scripts/aes-php.html Aes Ctr <PHP>
http://www.movable-type.co.uk/scripts/aes.html Aes Ctr <javascript> => github: https://github.com/chrisveness/crypto
http://aesencryption.net/ AES encryption <PHP/JAVA> =>Java驗證未過,可能是當初測時編碼問題
https://code.google.com/p/crypto-js/#AES crypto-js<javascript>
http://www.cnblogs.com/yipu/articles/3871576.html [转]php与java通用AES加密解密算法 (最初對接成功的範例,但有java中文編碼問題) <PHP/JAVA>
https://github.com/stevenholder/PHP-Java-AES-Encrypt PHP-Java-AES-Encrypt<PHP/JAVA>
http://www.java2s.com/Code/Java/Security/BasicIOexamplewithCTRusingAES.htm Basic IO example with CTR using AES : File Secure IO « Security « Java <JAVA>
http://magiclen.org/aes/ 在Java、Android、PHP實現AES加解密,並且互通的方式 <PHP/JAVA>
參考資料:
https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86 高階加密標準
http://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb How to choose an AES encryption mode (CBC ECB CTR OCB CFB)?
QQ
回覆刪除