前言:要跟java那邊用AES加密後的資料對接
在網路上找到許多不同語言的實作方式,但加密出來的結果都不一樣
原來是 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加密
$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 而是
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;
}
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加密
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://point-at-infinity.org/jsaes/ jsaes: AES in JavaScript <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)?