工作中經(jīng)常涉及和不同編程語(yǔ)言使用3DES加解密的對(duì)接問(wèn)題米碰,很多語(yǔ)言對(duì)于同一種加密算法的細(xì)節(jié)處理還是有些出入的补憾,典型的如加密模式叛拷、填充方法。
3DES加解密簡(jiǎn)介
DES使用分塊加密浴鸿,加密密鑰為56位井氢,塊大小為64位。由于56位版本的DES加密非常容易破解(一般機(jī)器一天以內(nèi))赚楚,就有了和DES兼容性較好的3DES算法毙沾。
原理
3DES加密算法表示為C=EncryptK3(DecryptK2(EncryptK1(message)
,如果K1宠页、K2左胞、K3為密鑰,如果各不相同举户,則相當(dāng)于加密密鑰長(zhǎng)度為112(由于中途相遇攻擊)烤宙;解密算法表示為message=DecryptK1((EncryptK2(DecryptK3(C)))
以加密算法舉例:
- 使用了3次DES算法,有3個(gè)密鑰
- 加密時(shí)第一個(gè)密鑰K1用來(lái)加密消息(P)俭嘁,輸出C1密文
- 第二個(gè)密鑰K2用來(lái)解密C1躺枕,輸出C2密文
- 第三個(gè)密鑰K3用來(lái)加密C2,輸出C3密文
分組密碼工作模式
分組密碼的工作模式允許使用同一個(gè)分組密碼密鑰對(duì)多于一塊的數(shù)據(jù)進(jìn)行加密,并保證其安全性拐云。分組密碼自身只能加密長(zhǎng)度等于密碼分組長(zhǎng)度的單塊數(shù)據(jù)罢猪,若要加密變長(zhǎng)數(shù)據(jù),則數(shù)據(jù)必須先被劃分為一些單獨(dú)的密碼塊叉瘩。通常而言膳帕,最后一塊數(shù)據(jù)也需要使用合適填充方式將數(shù)據(jù)擴(kuò)展到匹配密碼塊大小的長(zhǎng)度。一種工作模式描述了加密每一數(shù)據(jù)塊的過(guò)程薇缅,并常常使用基于一個(gè)通常稱為初始化向量的附加輸入值以進(jìn)行隨機(jī)化危彩,以保證安全。
對(duì)于分組密碼工作模式的介紹可參考wiki
常見(jiàn)的工作模式包括ECB泳桦,CBC汤徽,OFB和CFB等。
分組密碼初始化向量(IV)
用于將加密隨機(jī)化的一個(gè)位塊灸撰,由此即使同樣的明文被多次加密也會(huì)產(chǎn)生不同的密文谒府,避免了較慢的重新產(chǎn)生密鑰的過(guò)程。
分組密碼填充
塊密碼只能對(duì)確定長(zhǎng)度的數(shù)據(jù)塊進(jìn)行處理梧奢,而消息的長(zhǎng)度通常是可變的狱掂。因此部分模式(即ECB和CBC)需要最后一塊在加密前進(jìn)行填充演痒。
加密后字節(jié)轉(zhuǎn)文本
對(duì)消息加密后通常會(huì)得到字節(jié)數(shù)組亲轨,在傳輸時(shí)一般會(huì)使用BASE64或HEX等進(jìn)行編碼。
多語(yǔ)言舉例
統(tǒng)一起見(jiàn)鸟顺,工作模式選擇ECB惦蚊,填充模式選擇PKCS5Padding或使用8-byte塊大小的PKCS7Padding(兩種填充模式差別可參考StackExchange),使用base64將加密后字節(jié)轉(zhuǎn)換為文本密文讯嫂。
大家在使用時(shí)蹦锋,一定記得要明確工作模式(mode of operation)和填充模式(padding),因?yàn)?em>不同語(yǔ)言(甚至同一種語(yǔ)言不同版本)中對(duì)于工作模式和填充模式的實(shí)現(xiàn)是可以自由選擇的欧芽。
Java
代碼
public static String encrypt(String text, String key){
try {
DESedeKeySpec spec = new DESedeKeySpec(key.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("desede");
Key destKey = keyFactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede" + "/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, destKey);
byte[] textBs = text.getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(textBs);
BASE64Encoder b64encoder = new BASE64Encoder();
return b64encoder.encode(cipherText);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
}
public static String decrypt(String decryptedText, String key){
try {
BASE64Decoder b64decoder = new BASE64Decoder();
byte[] textBs = b64decoder.decodeBuffer(decryptedText);
DESedeKeySpec spec = new DESedeKeySpec(key.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("desede");
Key destKey = keyFactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede" + "/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, destKey);
byte[] cipherText = cipher.doFinal(textBs);
return new String(cipherText,"UTF-8");
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
注意
注意在使用Cipher.getInstance
選擇加密解密算法時(shí)莉掂,如果選擇desede,也即忽略工作模式和填充模式后千扔,將使用默認(rèn)的"/ECB/PKCS5Padding"憎妙。由于其他語(yǔ)言的默認(rèn)方式可能不同,將導(dǎo)致不同語(yǔ)言在加解密時(shí)異常曲楚。
使用到生產(chǎn)環(huán)境時(shí)厘唾,需要處理異常
Python
代碼
Python2
# encoding:utf-8
from Crypto.Cipher import DES3
import base64
# 加密模式 ECB , 填充模式 PKCS5Padding
BS = DES3.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
'''
DES3加密
text 待加密字符串
key 密鑰,使用appsecret
'''
def encrypt(text, key):
text = pad(text)
cipher = DES3.new(key,DES3.MODE_ECB)
m = cipher.encrypt(text)
m = base64.b64encode(m)
return m.decode('utf-8')
'''
DES3解密
decrypted_text 解密字符串
key 密鑰龙誊,使用appsecret
'''
def decrypt(decrypted_text, key):
text = base64.b64decode(decrypted_text)
cipher = DES3.new(key, DES3.MODE_ECB)
s = cipher.decrypt(text)
s = unpad(s)
return s.decode('utf-8')
if __name__ == '__main__':
print encrypt('Hello','1234567887654321')
print decrypt('qO8nDeYzqTs=','1234567887654321')
Python3
# encoding:utf-8
from Crypto.Cipher import DES3
import base64
# 加密模式 ECB , 填充模式 PKCS5Padding
BS = DES3.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-s[-1]]
'''
DES3加密
參數(shù):
text str 待加密字符串
key str 密鑰抚垃,使用appsecret
返回:
str 加密后的字符串
'''
def encrypt(text, key):
text = pad(text)
cipher = DES3.new(key,DES3.MODE_ECB)
m = cipher.encrypt(text) # m is bytes
#m = base64.encodestring(m) # 會(huì)追加\n
m = base64.b64encode(m)
decrypted_text = m.decode('utf-8')
return decrypted_text
'''
DES3解密
參數(shù):
decrypted_text str 解密字符串
key str 密鑰,使用appsecret
返回:
str 解密后的原始字符串
'''
def decrypt(decrypted_text, key):
decrypted_bytes = bytes(decrypted_text, encoding='utf-8')
#text = base64.decodestring(decrypted_bytes) #
text = base64.b64decode(decrypted_bytes) #
cipher = DES3.new(key, DES3.MODE_ECB)
s = cipher.decrypt(text)
s = unpad(s)
s = s.decode('utf-8') # unpad and decode bytes to str
return s
if __name__ == '__main__':
print(encrypt('Hello','1234567887654321'))
print(decrypt('qO8nDeYzqTs=','1234567887654321'))
注意
注意base64的encodestring已不推薦使用,且會(huì)在末尾加入"\n",使用b64encode代替鹤树。
python2中cipher.decrypt
和cipher.encrypt
以及base64.b64encode
和base64.b64decode
返回的是str
铣焊,而python3中返回的是bytes
。所以python3中unpad時(shí)無(wú)需再使用ord獲取字符的對(duì)應(yīng)ascii數(shù)值罕伯。
NodeJS
代碼
var crypto = require('crypto');
var key = '123456781234567812345678'; //加密的秘鑰 app secret
var demo_encryped = 'oVmfzWxhH88=';
var demo_decryped = 'Hello';
encrypt = function (str) {
var cipher = crypto.createCipheriv('des-ede3', key, new Buffer(0));
var crypted = cipher.update(str, 'utf8', 'base64'); // data[, input_encoding][, output_encoding]
crypted += cipher.final('base64');
return crypted;
}
decrypt = function(str) {
var decipher = crypto.createDecipheriv('des-ede3', key, new Buffer(0));
var dec = decipher.update(str, 'base64', 'utf8');
dec += decipher.final('utf8');
return dec;
}
console.log(encrypt(demo_decryped))
console.log(decrypt(demo_encryped))
注意
nodejs中的crypto
依賴openssl粗截,使用>openssl list-cipher-algorithms
獲取支持的算法列表。 如果需要選擇其他的算法捣炬,可參考o(jì)penssl熊昌。
desede其實(shí)是3des的別稱(反過(guò)來(lái)說(shuō)也可以)。
注意使用nodejs時(shí)湿酸,要使用createDecipheriv
婿屹,IV可傳入new Buffer(0)
CSharp
代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
namespace ConsoleApplication4
{
class Program
{
public static string Encrypt3DES(string a_strString, string a_strKey)
{
TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider();
DES.Key = ASCIIEncoding.ASCII.GetBytes(a_strKey);
DES.Mode = CipherMode.ECB;
ICryptoTransform DESEncrypt = DES.CreateEncryptor();
byte[] Buffer = UTF8Encoding.UTF8.GetBytes(a_strString);
return Convert.ToBase64String(DESEncrypt.TransformFinalBlock(Buffer, 0, Buffer.Length));
}
public static string Decrypt3DES(string a_strString, string a_strKey)
{
TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider();
DES.Key = ASCIIEncoding.ASCII.GetBytes(a_strKey);
DES.Mode = CipherMode.ECB;
DES.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
ICryptoTransform DESDecrypt = DES.CreateDecryptor();
string result = "";
try
{
byte[] Buffer = Convert.FromBase64String(a_strString);
byte[] temp = DESDecrypt.TransformFinalBlock(Buffer, 0, Buffer.Length);
result = UTF8Encoding.UTF8.GetString(temp);
}
catch (Exception e)
{
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
}
return result;
}
static void Main()
{
// Console.WriteLine("hello world");
// Console.ReadLine();
System.Text.Encoding utf8 = System.Text.Encoding.UTF8;
string sourceTxt = "Hello";
string appSecert = "123456781234567812345678";
string result = Encrypt3DES(sourceTxt, appSecert);
Console.WriteLine(result);
Console.ReadLine();
string result2 = Decrypt3DES(result, appSecert);
Console.WriteLine(result2);
Console.ReadLine();
}
}
}
PHP
代碼
<?php
//定義3DES加密key
define('CRYPT_KEYS', '123456781234567812345678');
class DES {
private $key = CRYPT_KEYS;
//只有CBC模式下需要iv,其他模式下iv會(huì)被忽略
private $iv = '';
/**
* 加密
*/
public function encrypt($value) {
//先確定加密模式推溃,此處以ECB為例
$td = mcrypt_module_open ( MCRYPT_3DES,'', MCRYPT_MODE_ECB,'');
//$iv = pack ( 'H16', $this->iv );
$value = $this->PaddingPKCS7 ( $value ); //填充
//$key = pack ( 'H48', $this->key );
mcrypt_generic_init ( $td, $this->key, $this->iv);
$ret = base64_encode ( mcrypt_generic ( $td, $value ) );
mcrypt_generic_deinit ( $td );
mcrypt_module_close ( $td );
return $ret;
}
/**
* 解密
*/
public function decrypt($value) {
$td = mcrypt_module_open ( MCRYPT_3DES, '', MCRYPT_MODE_ECB, '' );
//$iv = pack ( 'H16', $this->iv );
//$key = pack ( 'H48', $this->key );
mcrypt_generic_init ( $td, $this->key,$this->iv );
$ret = trim ( mdecrypt_generic ( $td, base64_decode ( $value ) ) );
$ret = $this->UnPaddingPKCS7 ( $ret );
mcrypt_generic_deinit ( $td );
mcrypt_module_close ( $td );
return $ret;
}
private function PaddingPKCS7($data) {
$padlen = 8 - strlen( $data ) % 8 ;
for($i = 0; $i < $padlen; $i ++)
$data .= chr( $padlen );
return $data;
}
private function UnPaddingPKCS7($data) {
$padlen = ord (substr($data, (strlen( $data )-1), 1 ) );
if ($padlen > 8 )
return false;
for($i = -1*($padlen-strlen($data)); $i < strlen ( $data ); $i ++) {
if (ord ( substr ( $data, $i, 1 ) ) != $padlen)return false;
}
return substr ( $data, 0, -1*($padlen-strlen ( $data ) ) );
}
}
CPP
代碼
#include <string>
#include <openssl/des.h>
#include <openssl/evp.h>
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
class Des {
private:
static const int CFBMODE = 64;
public:
static std::string encrypt(std::string tmp_key, std::string in) {
unsigned char key[24];
memset(key, 0, sizeof(key));
memcpy(key, tmp_key.c_str(), sizeof(key));
DES_key_schedule schedule;
DES_key_schedule schedule2;
DES_key_schedule schedule3;
DES_cblock desKey = { 0 };
memcpy(desKey, key, 8);
DES_set_key_unchecked(&desKey, &schedule);
memcpy(desKey, key + 8, 8);
DES_set_key_unchecked(&desKey, &schedule2);
memcpy(desKey, key + 16, 8);
DES_set_key_unchecked(&desKey, &schedule3);
int i = 0;
const size_t paddingLength = (8 - in.length() % 8);
unsigned char padding[8];
memset(padding, 8 - (in.length() % 8), 8);
if (paddingLength)
{
in.append((char*)padding, paddingLength);
}
size_t dataLength = paddingLength + in.length();
char tmp_buf[8];
memset(tmp_buf, 0, 8);
std::string data;
//data.reserve(dataLength);
unsigned char input[8];
for (size_t i = 0; i < (dataLength / 8); i++)
{
/*
DES_ede3_cfb_encrypt((const unsigned char*)in.c_str() + (i * 8), (unsigned char*)tmp_buf, CFBMODE, 8, &schedule,
&schedule2, &schedule3, &iv,
DES_ENCRYPT);
*/
//memset(input, 0, 8);
memcpy(input, in.c_str() + (i * 8), 8);
DES_ecb3_encrypt((const_DES_cblock*)input, (DES_cblock*)tmp_buf,&schedule, &schedule2, &schedule3, DES_ENCRYPT);
data.append(tmp_buf, sizeof(tmp_buf));
}
return data;
}
static std::string decrypt(std::string tmp_key, std::string in) {
//偏移向量
//24位加密key仇穗,3des下秘鑰必須為24位
unsigned char key[24];
memset(key, 0, sizeof(key));
memcpy(key, tmp_key.c_str(), sizeof(key));
DES_key_schedule schedule;
DES_key_schedule schedule2;
DES_key_schedule schedule3;
DES_cblock desKey = { 0 };
DES_cblock iv = { 0 };
memcpy(desKey, key, 8);
DES_set_key_unchecked(&desKey, &schedule);
memcpy(desKey, key + 8, 8);
DES_set_key_unchecked(&desKey, &schedule2);
memcpy(desKey, key + 16, 8);
DES_set_key_unchecked(&desKey, &schedule3);
int i = 0;
std::string padding;
unsigned char input[8];
unsigned char tmp_buf[8];
//memset(tmp_buf, 0, 8);
for (size_t i = 0; i < (in.length() / 8); i++)
{
/*
DES_ede3_cfb_encrypt((const unsigned char*)in.c_str() + (i * 8), (unsigned char*)tmp_buf, CFBMODE, 8, &schedule,
&schedule2, &schedule3, &iv,
DES_DECRYPT);
*/
memcpy(input, in.c_str() + (i * 8), 8);
DES_ecb3_encrypt((const_DES_cblock*)input, (DES_cblock*)tmp_buf, &schedule,
&schedule2, &schedule3, DES_DECRYPT);
if (i == ((in.length() / 8) - 1) && tmp_buf[7] >= 0x01 && tmp_buf[7] <= 0x08)
{
padding.append((char *)tmp_buf, 8 - tmp_buf[7]);
}
else
{
padding.append((char *)tmp_buf, 8);
}
}
return padding;
}
};
C++不懂,同事幫忙寫(xiě)的昵观。
以上代碼可以在我的Github上找到箫爷。
參考:
1 A Security Site
2 使用 PyCrypto 進(jìn)行 AES/ECB/PKCS#5(7) 加密
3 https://github.com/eXcellme/tripledes-demo
4 PKCS5Padding和PKCS7Padding的區(qū)別
5 StackOverflow Difference between DESede and TripleDES for cipher.getInstance()
6 Python2.7 base64 doc , Python3.6 base64 doc
7 GrepCode com.sun.crypto.provider.DESedeCipher OpenJDK8實(shí)現(xiàn)
8 YouTube - AES DES 3DES簡(jiǎn)介
9 YouTube - ECB vs CBC
10 Nodejs crypto api
11 中途相遇攻擊Wiki
12 分組密碼工作模式Wiki