多语言3DES加解密举例

工作中经常涉及和不同编程语言使用3DES加解密的对接问题,很多语言对于同一种加密算法的细节处理还是有些出入的,典型的如加密模式、填充方法。

3DES加解密简介

DES使用分块加密,加密密钥为56位,块大小为64位。由于56位版本的DES加密非常容易破解(一般机器一天以内),就有了和DES兼容性较好的3DES算法。

原理

3DES加密算法表示为C=EncryptK3(DecryptK2(EncryptK1(message),如果K1、K2、K3为密钥,如果各不相同,则相当于加密密钥长度为112(由于中途相遇攻击);解密算法表示为message=DecryptK1((EncryptK2(DecryptK3(C)))

以加密算法举例:

  • 使用了3次DES算法,有3个密钥
  • 加密时第一个密钥K1用来加密消息(P),输出C1密文
  • 第二个密钥K2用来解密C1,输出C2密文
  • 第三个密钥K3用来加密C2,输出C3密文

分组密码工作模式

分组密码的工作模式允许使用同一个分组密码密钥对多于一块的数据进行加密,并保证其安全性。分组密码自身只能加密长度等于密码分组长度的单块数据,若要加密变长数据,则数据必须先被划分为一些单独的密码块。通常而言,最后一块数据也需要使用合适填充方式将数据扩展到匹配密码块大小的长度。一种工作模式描述了加密每一数据块的过程,并常常使用基于一个通常称为初始化向量的附加输入值以进行随机化,以保证安全。

对于分组密码工作模式的介绍可参考wiki

常见的工作模式包括ECB,CBC,OFB和CFB等。

分组密码初始化向量(IV)

用于将加密随机化的一个位块,由此即使同样的明文被多次加密也会产生不同的密文,避免了较慢的重新产生密钥的过程。

分组密码填充

块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的。因此部分模式(即ECB和CBC)需要最后一块在加密前进行填充

加密后字节转文本

对消息加密后通常会得到字节数组,在传输时一般会使用BASE64或HEX等进行编码。

多语言举例

统一起见,工作模式选择ECB,填充模式选择PKCS5Padding或使用8-byte块大小的PKCS7Padding(两种填充模式差别可参考StackExchange),使用base64将加密后字节转换为文本密文。

大家在使用时,一定记得要明确工作模式(mode of operation)和填充模式(padding),因为不同语言(甚至同一种语言不同版本)中对于工作模式和填充模式的实现是可以自由选择的

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选择加密解密算法时,如果选择desede,也即忽略工作模式和填充模式后,将使用默认的"/ECB/PKCS5Padding"。由于其他语言的默认方式可能不同,将导致不同语言在加解密时异常。

使用到生产环境时,需要处理异常

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加密
  参数:
    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) # 会追加\n
    m = base64.b64encode(m)
    decrypted_text = m.decode('utf-8')
    return decrypted_text

'''
  DES3解密
  参数:
    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已不推荐使用,且会在末尾加入"\n",使用b64encode代替。

python2中cipher.decryptcipher.encrypt以及base64.b64encodebase64.b64decode返回的是str,而python3中返回的是bytes。所以python3中unpad时无需再使用ord获取字符的对应ascii数值。

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获取支持的算法列表。 如果需要选择其他的算法,可参考openssl。

desede其实是3des的别称(反过来说也可以)。

注意使用nodejs时,要使用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会被忽略 
    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++不懂,同事帮忙写的。

以上代码可以在我的Github上找到。


参考:
1 A Security Site
2 使用 PyCrypto 进行 AES/ECB/PKCS#5(7) 加密
3 https://github.com/eXcellme/tripledes-demo
4 PKCS5Padding和PKCS7Padding的区别
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实现
8 YouTube - AES DES 3DES简介
9 YouTube - ECB vs CBC
10 Nodejs crypto api
11 中途相遇攻击Wiki
12 分组密码工作模式Wiki

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 本文主要介绍移动端的加解密算法的分类、其优缺点特性及应用,帮助读者由浅入深地了解和选择加解密算法。文中会包含算法的...
    苹果粉阅读 11,305评论 5 29
  • 0x01 目录 常见编码: ASCII编码 Base64/32/16编码 shellcode编码 Quoted-p...
    H0f_9阅读 12,209评论 2 17
  • 这篇文章主要讲述在Mobile BI(移动商务智能)开发过程中,在网络通信、数据存储、登录验证这几个方面涉及的加密...
    雨_树阅读 2,235评论 0 6
  • 1 基础 1.1 对称算法 描述:对称加密是指加密过程和解密过程使用相同的密码。主要分:分组加密、序列加密。 原理...
    御浅永夜阅读 2,210评论 1 4
  • 关于拖延症的文章很多,这确实让很多人苦不堪言,本人就深受其害,最近无意看到了一则把牛顿定律与拖延症关联到一起的文章...
    tballer阅读 476评论 4 2