Java加密与解密技术(下)

开源组件

Bouncy Castle( http://www.bouncycastle.org/ )提供了一系列算法支持实现,并可以跻身于JCE框架之下,以提供者的方式纳入其中。而且,Bouncy Castle是一款开源组件,你不必为版权问题而伤脑筋。此外,Bouncy Castle提供了与Base64和十六进制编码转换相关的实现。

Commons Codec( http://commons.apache.org/codec/ )是国际开源组织Apache( http://www.apache.org/ )旗下的一款开源软件。它与Bouncy Castle不同,并未对Java提供扩展加密算法。仅仅是对Java提供的API做了扩展,提供了更加易用的API。

加密组件Bouncy Castle

Java提供了多种算法支持,但并不完善。许多加密强度较高的算法,Java未能提供。在前面,我们提到了java.security文件,它位于${JAVA_HOME}/jre/lib/security/ext目录下,用于提供者配置。

如果需要使用Java不支持的算法,如MD4和SM等。可以在继续沿用Java的API前提下,通过在JRE环境中或代码中配置开源组件包Bouncy Castle,加入对应的提供者,获得相应的算法支持。

方式一:全局设置

将BC的jar文件放到${JAVA_HOME}/jre/lib/ext目录。修改${JAVA_HOME}/jre/lib/security/java.security

# 增加BouncyCastleProvider
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

方式二:单JVM实例设置

static {
    if (Security.getProvider("BC") == null) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

使用

MessageDigest md = MessageDigest.getInstance("MD4", "BC");

每个提供者都有简称,Bouncy Castle提供者简称“BC”,因此我们可以通过上述方式使用BouncyCastleProvider

相关API

Bouncy Castle的API主要包含了以下几个方面。

  • JCE工具及其扩展包:仅包括org.bouncycastle.jce包。这是对JCE框架的支持。其中定义了一些扩展算法的接口与实现,如ECC和ElGamal算法。
  • JCE支持者和测试包:包括org.bouncycastle.jce.provider包及其子包。前面提到的Bouncy Castle的安全提供者BouncyCastleProvider就位于该包中。
  • 轻量级加密包:包括org.bouncycastle.crypto包及其子包。这个包完成了扩展算法的实现。
  • OCSP和OpenSSL PEM支持包:包括org.bouncycastle.ocsp包及其子包和org.bouncycastle.openssl包及其子包。这两个包都是与数字证书相关的支持包。OCSP(Online Certificate Status Protocol,在线证书状态协议)用于鉴定所需证书的(撤销)状态。具体协议内容请查看RFC 2560( http://www.ietf.org/rfc/rfc2560.txt )。OpenSSL用于管理数字证书,包括证书的申请和撤销等。
  • ASN.1编码支持包:包括org.bouncycastle.asn1包及其子包,该包体积最为庞大。标准的ASN.1编码规则有基本编码规则(Basic Encoding Rules,BER)、规范编码规则(Canonical Encoding Rules,CER)、唯一编码规则(Distinguished Encoding Rules,DER)、压缩编码规则(Packed Encoding Rules,PER)和XML编码规则(XML Encoding Rules,XER)。我们在导出数字证书时,常常会使用到DER编码。
  • 工具包:包括org.bouncycastle.util包及其子包。提供了很多与编码转换有关的工具类,如Base64编码和十六进制编码。
  • 其他包:包括org.bouncycastle.mozilla包及其子包和org.bouncycastle.x509包及其子包。org.bouncycastle.mozilla包用于支持基于Mozilla(网景)浏览器的公钥签名和身份认证。org.bouncycastle.x509包用于基于支持X.509格式的数字证书。

辅助工具Commons Codec

Commons Codec是Apache旗下的一款开源软件,主要用于编码格式的转换,如Base64、二进制、十六进制、字符集和Url编码的转换。甚至,Commons Codec还提供了语音编码的转换。除此之外,Commons Codec还对Java原生的消息摘要算法做了良好的封装,提高了方法的易用性。

相关API

Commons Codec的API主要包含了以下几个方面。

  • org.apache.commons.codec:该包内主要定义了一些编码转换的接口。
  • org.apache.commons.codec.binary:该包内主要完成了编码转换实现,如Base64、二进制、十六进制和字符集编码。
  • org.apache.commons.codec.digest:该包内仅有一个实现类DigestUtils,它是对Java原生消息摘要实现的改进。
  • org.apache.commons.codec.language:该包内主要完成了语言和语音编码器实现。
  • org.apache.commons.codec.net:该包内主要完成了网络相关的编码和解码,如Url编码/解码。

电子邮件传输算法——Base64

Base64算法的由来

Base64算法最早应用于解决电子邮件传输的问题。在早期,由于“历史问题”,电子邮件只允许ASCII码字符。如果要传输一封带有非ASCII码字符的电子邮件,当它通过有“历史问题”的网关时就可能出现问题。这个网关很可能会对这个非ASCII码字符的二进制位做调整,即将这个非ASCII码的8位二进制码的最高位置为0。此时用户收到的邮件就会是一封纯粹的乱码邮件了。基于这个原因产生了Base64算法。

Base64算法的定义

Base64是一种基于64个字符的编码算法,根据RFC 2045( http://www.ietf.org/rfc/rfc2045.txt )的定义:“Base64内容传送编码是一种以任意8位字节序列组合的描述形式,这种形式不易被人直接识别”。经过Base64编码后的数据会比原始数据略长,经过Base64编码后的数据会比原始数据略长,为原来的4/3倍。经Base64编码后的字符串的字符数是以4为单位的整数倍。

RFC 2045还规定,在电子邮件中,每行为76个字符,每行末需添加一个回车换行符("\r\n"),不论每行是否够76个字符,都要添加一个回车换行符。但在实际应用中,往往根据实际需要忽略了这一要求。

在RFC 2045文件中给出了表所示的字符映射表:

Base64-Mapping

这张字符映射表中,Value指的是十进制编码,Encoding指的是字符,共映射了64个字符,这也是Base64算法命名的由来。映射表的最后一个字符是等号,它是用来补位的。

其实,Base64算法还有几个同胞兄弟,如Base32和Base16算法。为了能在http请求中以Get方式传递二进制数据,由Base64算法衍生出了Url Base64算法。

Url Base64算法主要是替换了Base64 字符映射表中的第62和63个字符,也就是将“+”和“/”符号替换成了“-”和“_”符号。但对于补位符“=”,一种建议是使用“~”符号,另一种建议是使用“.”符号。其中,由于“~”符号与文件系统冲突,不建议使用;而对于“.”符号,如果出现连续两次,则认为是错误。对于补位符的问题,Bouncy Castle和Commons Codec有差别:Bouncy Castle使用“.”作为补位符,而Commons Codec则完全杜绝使用补位符。

Base64在Bouncy Castle和Commons Codec实现方式的差异

Bouncy Castle和Commons Codec都提供了Base64算法实现,但是否遵循RFC 2045定义,即在编码后的字符串末尾是否添加回车换行符,是两种实现的唯一差别!

Bouncy Castle遵循了一般Base64算法编码。

Commons Codec提供了Base64算法的两种实现标准:一种是遵循一般Base64算法实现;另一种是遵循RFC 2045定义。此外,Commons Codec还提供了Base64算法的定制实现,可以自定每行字符数和行末符号。同时,Commons Codec还提供了基于Base64算法的输入输出流实现。

综上所述,单纯的Base64算法在使用上比Commons Codec更具优势。

JDK8+的java.util.Base64提供了遵循RFC 2045和RFC 4648的实现。

Url Base64在Bouncy Castle和Commons Codec实现方式的差异

Bouncy Castle的Url Base64算法实现与RFC 4648定义较为接近,构建定长Base64编码,使用“.”符号作为填充符。

Commons Codec的Url Base64算法实现遵照了RFC 4648绝大部分定义,为避免可能的错误,使用不定长Base64编码,抛弃了填充符。

对于这两种实现,从实用性的角度讲,通过Url参数方式传输数据往往要求数据长度尽量缩短,以缩短Url长度,避免网关限制,减少网络传输时间。从这一点讲,Commons Codec的Url Base64实现较为实用。

应用举例

  • 电子邮件传输
  • 网络数据传输
  • 密钥存储
  • 数字证书存储

OpenSSL的Base64实现是遵循RFC 2045标准的。

验证数据完整性——消息摘要算法

消息摘要算法又称散列算法,其核心在于散列函数的单向性。即通过散列函数可获得对应的散列值,但不可通过该散列值反推其原始信息。这是消息摘要算法的安全性的根本所在。

消息摘要算法主要分为三大类:MD(Message Digest,消息摘要算法)、SHA-1(Secure Hash Algorithm,安全散列算法)和Hmacmd5(Message Authentication Code,消息认证码算法),常用于验证数据的完整性,是数字签名算法的核心算法。

如前文所述,MD5、SHA和HMAC都属于消息摘要算法,它们是三大消息摘要算法的主要代表。MD系列算法包括MD2、MD4和MD5共3种算法;SHA算法主要包括其代表算法SHA-1和SHA-1算法的变种SHA-2系列算法(包含SHA-224、SHA-256、SHA-384和SHA-512);MAC算法综合了上述两种算法,主要包括HmacMD5、HmacSHA1、HmacSHA256、HmacSHA384和HmacSHA512算法。

基于这些消息摘要算法,又衍生出了RipeMD系列(包含RipeMD128、RipeMD160、RipeMD256、RipeMD320)、Tiger、GOST3411和Whirlpool算法。

MD算法的Java实现

MD系列算法的实现是通过MessageDigest类来完成的,如果需要以流的处理方式完成消息摘要,则需要使用DigestInputStream和DigestOutputStream。Java 8仅支持MD2和MD5两种算法,通过第三方加密组件包Bouncy Castle,可支持MD4算法。

import java.security.MessageDigest;

/**
 * MD消息摘要组件
 * @since 1.0
 */
public abstract class MDCoder {

  /**
   * MD2消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要
   * @throws Exception
   */
  public static byte[] encodeMD2(byte[] data) throws Exception {
    // 初始化MessageDigest
    MessageDigest md = MessageDigest.getInstance("MD2");
    // 执行消息摘要
    return md.digest(data);
  }

  /**
   * MD5消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要 
   * @throws Exception
   */
  public static byte[] encodeMD5(byte[] data) throws Exception {
    // 初始化MessageDigest
    MessageDigest md = MessageDigest.getInstance("MD5");
    // 执行消息摘要
    return md.digest(data);
  }
}

Commons Codec

DigestUtils类(它位于org.apache.commons.codec.digest包中)。DigestUtils类是对Sun提供的MessageDigest类的一次封装,提供了MD5和SHA系列消息摘要算法的实现。

import org.apache.commons.codec.digest.DigestUtils;

/**
 * MD5消息摘要组件
 * @since 1.0
 */
public abstract class MD5Coder {

  /**
   * MD5消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要
   * @throws Exception
   */
  public static byte[] encodeMD5(String data) throws Exception {
    // 执行消息摘要
    return DigestUtils.md5(data);
  }

  /**
   * MD5消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要
   * @throws Exception
   */
  public static String encodeMD5Hex(String data) throws Exception {
    // 执行消息摘要
    return DigestUtils.md5Hex(data);
  }
}

SHA算法的Java实现

SHA算法是基于MD4算法实现的,作为MD算法的继任者,成为了新一代的消息摘要算法的代表。SHA与MD算法不同之处主要在于摘要长度,SHA算法的摘要更长,安全性更高。

在Java 8中,MessageDigest类支持MD算法的同时也支持SHA算法,几乎涵盖了我们目前所知的全部SHA系列算法,主要包含SHA-1、SHA-224、SHA-256、SHA-384和SHA-512四种算法。

“SHA”是“SHA-1”的简称,两种算法名称等同。

import java.security.MessageDigest; 

/**
 * SHA消息摘要组件
 * @since 1.0
 */
public abstract class SHACoder {

  /**
   * SHA-1消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要 
   * @throws Exception
   */
  public static byte[] encodeSHA(byte[] data) throws Exception {
    // 初始化MessageDigest
    MessageDigest md = MessageDigest.getInstance("SHA");
    // 执行消息摘要
    return md.digest(data);
  }
  
  /**
   * SHA-256消息摘要
   * @param data 待做摘要处理的数据
   * @return byte[] 消息摘要 
   * @throws Exception
   */
  public static byte[] encodeSHA256(byte[] data) throws Exception {
    // 初始化MessageDigest
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    // 执行消息摘要
    return md.digest(data);
  }
}

Commons Codec

DigestUtils类除了MD5算法外,还支持多种SHA系列算法,涵盖了Java 8所支持的全部SHA算法。

MAC算法的Java实现

MAC算法结合了MD5和SHA算法的优势,并加入密钥的支持,是一种更为安全的消息摘要算法。

Java 8中提供了HmacMD5、HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384和HmacSHA512等算法,而第三方加密组件包Bouncy Castle补充了HMACMD2、HMACMD4等算法支持。

MAC算法是带有密钥的消息摘要算法,所以实现起来要分两步:

1)构建密钥。
2)执行消息摘要。

// 初始化KeyGenerator
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
// 产生密钥
SecretKey secretKey = keyGenerator.generateKey();
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
// 初始化Mac
mac.init(secretKey);
// 执行消息摘要
byte[] data = mac.doFinal(data);

SM3算法的Bouncy Castle实现

SM3是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。相关标准为“GM/T 0004-2012 《SM3密码杂凑算法》”。

在商用密码体系中,SM3主要用于数字签名及验证、消息认证码生成及验证、随机数生成等,其算法公开。据国家密码管理局表示,其安全性及效率与SHA-256相当。

byte[] message = "This is a message".getBytes();
digest = MessageDigest.getInstance("SM3", "BC");
byte[] result = digest.digest(message);

其他消息摘要算法

除了MD、SHA和MAC这三大主流消息摘要算法外,还有许多我们不了解的消息摘要算法,包括RipeMD系列(包含RipeMD128、RipeMD160、RipeMD256和RipeMD320)、Tiger、Whirlpool和GOST3411算法。

RipeMD系列算法与MAC系列算法相结合,又产生了HmacRipeMD128和HmacRipeMD160两种算法。

循环冗余校验算法——CRC算法

对于“奇偶校验码”和“循环冗余校验码”这样的名词大家应该都不会陌生。即便不是计算机专业出身,也一定使用过压缩软件WinRAR,在压缩文件列表中能看到一个标有“CRC32”字样的内容。

CRC(Cyclic Redundancy Check,循环冗余校验)是可以根据数据产生简短固定位数的一种散列函数,主要用来检测或校验数据传输/保存后出现的错误。生成的散列值在传输或储存之前计算出来并且附加到数据后面。在使用数据之前,对数据的完整性做校验。一般来说,循环冗余校验的值都是32位的二进制数,以8位十六进制字符串形式表示。它是一类重要的线性分组码,编码和解码方法简单,检错和纠错能力强,在通信领域广泛地用于实现差错控制。

初等数据加密——对称加密算法

目前已知的可通过Java语言实现的对称加密算法大约有20多种,Java 7仅提供了部分算法实现,如DES、DESede、AES、Blowfish,以及RC2和RC4算法等。其他算法(如IDEA算法)需要通过第三方加密软件包Bouncy Castle提供实现。

在对称加密算法中,DES算法最具有代表性,堪称典范;DESede是DES算法的变种;AES算法则作为DES算法的替代者;而IDEA算法作为一种强加密算法,成为电子邮件加密软件PGP(Pretty Good Privacy)的核心算法之一。

在Java实现层面上,DES、DESede、AES和IDEA这4种算法略有不同。

DES和DESede算法在使用密钥材料还原密钥时,建议使用各自相应的密钥材料实现类(DES算法对应DESKeySpec类,DESede算法对应DESedeKeySpec类)完成相应转换操作。

AES算法在使用密钥材料还原密钥时,则需要使用一般密钥材料实现类(SecretKeySpec类)完成相应转换操作。其他对称加密算法可参照该方式实现,如RC2、RC4、Blowfish以及IDEA等算法均可参照AES算法实现方式做相应实现。

IDEA算法实现Java 8未能提供,需要依赖第三方加密组件包Bouncy Castle提供支持,其他由Bouncy Castle提供支持的对称加密算法可参照该算法实现方式做相应实现。

数据加密标准——DES

DES(Data Encryption Standard)算法和DESede算法统称DES系列算法。DES算法是对称加密算法领域中的典型算法,为后续对称加密算法的发展奠定了坚实的基础。DESede算法基于DES算法进行三重迭代,增加了算法安全性。经过一番筛选,Rijndeal算法最终成为了AES算法。这期间,对称加密算法发展迅速,与Rijndeal算法竞争的算法包括Blowfish、Serpent等。IDEA算法也源于增加算法的安全性替代DES算法,诸多对称加密算法的发展均源于DES算法的研究而来。

1973年,美国国家标准局(National Bureau of Standards,NBS,即现在的美国国家标准技术研究所(National Institute of Standards and Technology,NIST))征求国家密码标准方案,IBM公司提交了自己研制的算法(Luciffer算法,于1971年末提出)。1977年7月15日,该算法被正式采纳,作为美国联邦信息处理标准生效,并很快应用到国际商用数据加密领域,成为事实标准,即数据加密标准(Data Encryption Standard,DES),DES算法由此诞生。

// 实例化密钥生成器
KeyGenerator kg = KeyGenerator.getInstance("DES/ECB/PKCS5Padding");
// 初始化
kg.init(56);
// 生成秘密密钥
SecretKey secretKey = kg.generateKey();
// 获得密钥的二进制编码形式
byte[] b = secretKey.getEncoded();
// 实例化DES密钥材料
DESKeySpec dks = new DESKeySpec(b);
// 实例化秘密密钥工厂
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES/ECB/PKCS5Padding");
// 生成秘密密钥
SecretKey secretKey = keyFactory.generateSecret(dks);

在实际应用中,密文通常以二进制数据传输/存储,而密钥通常会被转换为可见字符存储。如使用Base64编码或十六进制编码将不可见的二进制密钥转换为可见字符。当然,这里需要注意一点,若使用Base64编码,则编码后的信息将是原始信息长度的4/3倍。

“DES/ECB/PKCS5Padding”。这里的加密/解密算法中除了包含密钥算法(DES)外,还包含了工作模式(ECB)和填充方式(PKCS5Padding)。

三重DES——DESede

作为DES算法的一种改良,DESede算法针对其密钥长度偏短和迭代次数偏少等问题做了相应改进,提高了安全强度。DESede算法处理速度较慢,密钥计算时间较长,加密效率不高等问题使得对称加密算法的发展仍不容乐观。

高级数据加密标准——AES

DES算法漏洞的发现加速了对称加密算法的改进,通过对DES算法的简单改造得到的DESede算法虽然在一定程度上提升了算法安全强度。但DESede算法低效的加密实现和较慢的处理速度仍不能满足我们对安全的要求。AES算法正是基于这些缘由而诞生。

1997年,NIST发起了征集DES替代算法——AES(Advanced Encryption Standard,高级数据加密标准)算法的活动。1997年9月12日,NIST发布了征集算法的正式公告和具体细节,要求AES算法要比DESede算法快,至少与DESede算法一样安全,具有128位的分组长度,支持128位、192位和256位的密钥长度,同时要求AES要能够在世界范围内免费使用。

1998年8月20日,NIST在“第一次AES候选大会”上公布了满足条件的15个AES的候选算法,继而又从中筛选出5个候选算法,包括MARS、RC6、Rijndael、Serpent和Twofish。

2000年10月2日,由Daemen和Rijmen两位比利时人提出的Rijndael算法,以其密钥设置快、存储要求低,在硬件实现和限制存储的条件下性能优异当选AES算法。

经过验证,目前采用的AES算法能够有效抵御已知的针对DES算法的所有攻击方法,如部分差分攻击、相关密钥攻击等。至今,还没有AES破译的官方报道。

AES算法因密钥建立时间短、灵敏性好、内存需求低等优点,在各个领域得到广泛的研究与应用。

目前,AES常用于UMTS(Universal Mobile Telecommunications System,通用移动通信系统)。基于SSH(Secure Shell,安全外壳)协议的一些软件也使用了AES算法。

// 密钥算法
String KEY_ALGORITHM = "AES";
// 加密/解密算法 / 工作模式 / 填充方式
String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 还原密钥
// 生成秘密密钥
SecretKey secretKey = new SecretKeySpec("key byte[]", KEY_ALGORITHM);
// 实例化
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// 执行操作
cipher.doFinal(data);

国际数据加密标准——IDEA

早在NIST发布征集AES算法以前,就已经有人在找寻DES算法的替代算法了。IDEA算法的提出者未像DESede算法那样在原有DES算法的基础上做改进,而是独辟蹊径地寻求了突破性解决方案。IDEA算法早于AES算法作为DES算法的可选替代算法出现。

IDEA(International Data Encryption Algorithm,国际数据加密标准)算法是由旅居瑞士的中国青年学者来学嘉和著名密码专家James Massey于1990年提出的一种对称分组密码,并于1992年修改完成。

IDEA算法在美国之外提出并发展起来,避开了美国法律上对加密技术的诸多限制。因此,有关IDEA算法和实现技术的书籍均可自由出版和交流,极大地促进了IDEA算法的发展和完善。

IDEA算法是目前较为常用的电子邮件加密算法之一。电子邮件加密软件PGP(Pretty Good Privacy)使用了具有商业版权的IDEA算法,实现邮件加密/解密工作。

Java 8没有提供IDEA算法的相应实现,若需要使用该算法我们可以通过Bouncy Castle来完成。Bouncy Castle不仅提供了IDEA算法实现,包括其他Java 8未能支持的对称加密算法也可通过Bouncy Castele实现,如AES候选算法Rijndael、Serpent和Twofish等。

// 密钥算法
String KEY_ALGORITHM = "IDEA";
// 加密/解密算法 / 工作模式 / 填充方式
String CIPHER_ALGORITHM = "IDEA/ECB/ISO10126Padding";
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 还原密钥
// 生成秘密密钥
SecretKey secretKey = new SecretKeySpec("key byte[]", KEY_ALGORITHM);
// 实例化
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// 执行操作
cipher.doFinal(data);

基于口令加密——PBE

前文阐述了几种常用的对称加密算法,这些算法的应用模型(甚至包括实现)几乎同出一辙。但并不是所有的对称加密算法都是如此,PBE算法综合了上述对称加密算法和消息摘要算法的优势,形成了对称加密算法的一个特例。

PBE(Password Based Encryption,基于口令加密)算法是一种基于口令的加密算法,其特点在于口令由用户自己掌管,采用随机数(这里我们叫做盐)杂凑多重加密等方法保证数据的安全性。

PBE算法没有密钥的概念,密钥在其他对称加密算法中是经过算法计算得出的,PBE算法中则使用口令替代了密钥。

PBE算法并没有真正构建新的加密/解密算法,而是对我们已知的对称加密算法(如DES算法)做了包装。使用PBE算法对数据做加密/解密操作时,其实是使用了DES或AES等其他对称加密算法做了相应的操作。

既然PBE算法使用了我们较为常用的对称加密算法,那就无法回避密钥的问题。口令并不能替代密钥,密钥是经过加密算法计算得出的,但口令本身不可能很长,单纯的口令很容易通过穷举攻击方式破译,这就引入了“盐”。盐能够阻止字典攻击或预先计算的攻击,它本身是一个随机信息,相同的随机信息极不可能使用两次。将盐附加在口令上,通过消息摘要算法经过迭代计算获得构建密钥/初始化向量的基本材料,使得破译加密信息的难度加大。

PBE算法是对称加密算法的综合性算法,常见算法如PBEWithMD5AndDES,该算法使用了MD5和DES算法构建PBE算法。

// 算法
String ALGORITHM = "PBEWITHMD5andDES";
// 迭代次数
String ITERATION_COUNT = 100;
// 生成秘密密钥
// 密钥材料转换
PBEKeySpec keySpec = new PBEKeySpec("password".toCharArray());
// 实例化
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
// 生成密钥
SecretKey secretKey = keyFactory.generateSecret(keySpec);
// 实例化PBE参数材料
PBEParameterSpec paramSpec = new PBEParameterSpec("byte [] salt", ITERATION_COUNT);
// 实例化
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
// 执行操作
return cipher.doFinal(data);

国密算法——SM4分组密码算法

SM4无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。

KeyGenerator kg = KeyGenerator.getInstance("SM4");
kg.init(new SecureRandom());
SecretKey secretKey = kg.generateKey();
byte[] encoded = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(encoded, "SM4");
Cipher encryptionCipher = Cipher.getInstance("SM4");
encryptionCipher.init(ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = encryptionCipher.doFinal("/* byte[] data */")

Cipher decryptionCipher = Cipher.getInstance("SM4");
decryptionCipher.init(DECRYPT_MODE, secretKeySpec);
byte[] decrypted = decryptionCipher.doFinal("/* byte[] encrypted data */")

高等数据加密——非对称加密算法

相比与对称加密算法的单钥体系,非对称加密算法的双钥体系就更为安全。但非对称加密的缺点是加解密速度要远远慢于对称加密,在某些极端情况下,非对称加密算法甚至比非对称加密要慢1000倍。

非对称加密算法与对称加密算法的主要差别在于非对称加密算法用于加密和解密的密钥不相同,一个公开,称为公钥;一个保密,称为私钥。因此,非对称密码算法也称为双钥或公钥加密算法。

非对称加密算法解决了对称加密算法密钥分配问题,并极大地提高了算法安全性。多种B2C或B2B应用均使用非对称加密算法作为数据加密的核心算法。

非对称加密算法源于DH算法(Diffie-Hellman,密钥交换算法),由W.Diffie和M.Hellman共同提出。该算法为非对称加密算法奠定了基础,堪称非对称加密算法之鼻祖。
DH算法提出后,国际上相继出现了各种实用性更强的非对称加密算法,其构成主要是基于数学问题的求解,主要分为两类:

  • 基于因子分解难题。RSA算法是最为典型的非对称加密算法,该算法由美国麻省理工学院(MIT)的Ron Rivest、AdiShamir和Leonard Adleman三位学者提出,并以这三位学者的姓氏开头字母命名,称为RSA算法。RSA算法是当今应用范围最为广泛的非对称加密算法,也是第一个既能用于数据加密也能用于数字签名的算法。

  • 基于离散对数难题。ElGamal算法由Taher ElGamal提出,以自己的名字命名。该算法既可用于加密/解密,也可用于数字签名,并为数字签名算法形成标准提供参考。美国的DSS(Digital Signature Standard,数据签名标准)的DSA(Digital Signature Algorithm,数字签名算法)经ElGamal算法演变而来。

ECC(Elliptical Curve Cryptography,椭圆曲线加密)算法以椭圆曲线理论为基础,在创建密钥时可做到更快、更小,并且更有效。ECC 算法通过椭圆曲线方程式的性质产生密钥,而不是采用传统的方法利用大质数的积来产生。

在Java 6中仅提供了DH和RSA两种算法实现,而在Java 7中新增了ECDH算法实现,用于增强密钥交换算法,提升HTTPS服务的安全性。通过Boucy Castle可以获得ElGamal、SM2算法支持。

密钥交换算法——DH&ECDH

DH密钥交换算法的安全性基于有限域上的离散对数难题。基于这种安全性,通过DH算法进行密钥分配,使得消息的收发双方可以安全地交换一个秘密密钥,再通过这个密钥对数据进行加密和解密处理。

ECDH是基于椭圆曲线加密算法的密钥交换算法(这里将ECC简化为EC,同知椭圆曲线加密算法),密钥较短但密钥安全性更强,其原理与DH算法完全一致。

模型分析

我们以消息传递模型为例,甲方作为发送者,乙方作为接收者,分述甲乙双方如何构建密钥、交互密钥和加密数据。

首先,甲乙双方需要在收发消息前构建自己的密钥对。

甲乙两方构建密钥需要经过以下几个步骤:

1)由消息发送的一方构建密钥,这里由甲方构建密钥。
2)由构建密钥的一方向对方公布其公钥,这里由甲方向乙方发布公钥。
3)由消息接收的一方通过对方公钥构建自身密钥,这里由乙方使用甲方公钥构建乙方密钥。
4)由消息接收的一方向对方公布其公钥,这里由乙方向甲方公布公钥。

这里要注意的是,乙方构建自己的密钥对的时候需要使用甲方公钥作为参数。这是很关键的一点,如果缺少了这一环节则无法确保甲乙双方获得同一个秘密密钥,消息加密更无从谈起。

其次,假设甲乙双方事先约定好了用于数据加密的对称加密算法(如AES算法),并构建本地密钥(即对称加密算法中的秘密密钥),

甲方需要使用自己的私钥和乙方的公钥才能构建本地密钥,即用于数据加密/解密操作的对称加密算法的秘密密钥。

乙方构建本地密钥的方式与甲方相类似,同样需要通过使用自己的私钥和甲方的公钥构建本地密钥。

虽然,甲乙两方使用了不同的密钥来构建本地密钥,但甲乙两方得到密钥其实是一致的,我们可以通过后续的测试用例验证这一点。也正因于此,甲乙双方才能顺利地进行加密消息的传递。

最后,甲乙双方构建了本地密钥后,可按基于对称加密算法的消息传递模型完成消息传递。

构建DH算法甲方密钥对

// 实例化密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
// 初始化密钥对生成器
keyPairGenerator.initialize(1024);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 公钥
PublicKey publicKey = keyPair.getPublic();
// 私钥
PrivateKey privateKey = keyPair.getPrivate();

构建DH算法乙方密钥对

// 由甲方公钥构建乙方密钥
DHParameterSpec dhParamSpec = ((DHPublicKey) pubKey).getParams();
// 实例化密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyFactory.getAlgorithm());
// 初始化密钥对生成器
keyPairGenerator.initialize(dhParamSpec);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 公钥
PublicKey publicKey = keyPair.getPublic();
// 私钥
PrivateKey privateKey = keyPair.getPrivate();

如果要将密钥材料转换为密钥对象,可参考代码:

// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance("DH");
// 初始化公钥
// 公钥密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
// 产生公钥
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 初始化私钥
// 私钥密钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
// 产生私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);

完成甲乙方密钥的构建操作后,我们便可以完成本地密钥的构建。这里,要求使用不对称的公钥和私钥来构建本地密钥,即使用甲方私钥和乙方公钥构建甲方本地密钥;使用乙方私钥和甲方公钥构建乙方本地密钥。

// 实例化
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory.getAlgorithm());
// 初始化
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
// 生成本地密钥
SecretKey secretKey = keyAgree.generateSecret("AES");

典型非对称加密算法——RSA

DH算法的诞生为后续非对称加密算法奠定了基础,较为典型的对称加密算法(如ElGamal、RSA和ECC算法等)都是在DH算法提出后相继出现的,并且其算法核心都源于数学问题。

RSA算法基于大数因子分解难题,而ElGamal算法和ECC算法则是基于离散对数难题。

1978年,美国麻省理工学院(MIT)的Ron Rivest、Adi Shamir和Leonard Adleman三位学者提出了一种新的非对称加密算法,这种算法以这三位学者的姓氏开头字母命名,被称为RSA算法。

RSA算法是唯一被广泛接受并实现的通用公开加密算法,目前已经成为非对称加密算法国际标准。不仅如此,RSA算法既可用于数据加密也可用于数字签名。我们熟知的电子邮件加密软件PGP就采用了RSA算法对数据进行加密/解密和数字签名处理。

模型分析

我们仍以甲乙两方收发消息为例。甲方作为消息的发送方,乙方作为消息的接收方。我们假设甲乙双方在消息传递之前已将RSA算法作为消息传递的加密算法。为完成加密消息传递,甲乙双方需要以下操作:

1)由消息发送的一方构建密钥对,这里由甲方完成。
2)由消息发送的一方公布公钥至消息接收方,这里由甲方将公钥公布给乙方。

甲方向乙方发送数据时,使用私钥对数据做加密处理;乙方接收到加密数据后,使用公钥对数据做解密处理。

我们需要注意,在非对称加密算法领域中,对于私钥加密的数据,只能使用公钥解密。简言之,“私钥加密,公钥解密”。

相对于“私钥加密,公钥解密”的实现,RSA算法提供了另一种加密/解密方式:“公钥加密,私钥解密”。

常用非对称加密算法——ElGamal

在非对称加密算法中,几乎所有的算法都是基于数学问题而建立的:RSA算法基于大数因子分解数学难题,而ElGamal算法和ECC算法则基于离散对数问题。与典型非对称加密算法RSA算法相比,ElGamal算法则被称为常用非对称加密算法。

1985年,Taher ElGamal提出了一种基于离散对数问题的非对称加密算法,该算法既可用于加密,又可用于数字签名,是除了RSA算法外最具有代表性的公钥加密算法之一。Taher ElGamal用自己的名字定义了这种算法——ElGamal算法。

由于ElGamal算法具有较好的安全性,因此得到了广泛的应用。著名的美国数字签名标准(Digital Signature Standard,DSS)就是采用了ElGamal签名方案的一种变形——DSA(Digital Signature Algorithm)。ElGamal的一个不足之处是它的密文会成倍扩张。

ElGamal算法在构建密钥时的操作流程几乎与RSA算法完全一致。不同的是,这次密钥对的构建者换了主人。我们仍以甲乙两方收发消息为例,甲方作为消息的发送方,乙方作为消息的接收方。我们假设甲乙双方在消息传递之前已将ElGamal算法作为消息传递的加密算法。为完成加密消息传递,甲乙双方需要以下操作:

1)由消息发送的一方构建密钥对,这里由乙方完成。
2)由消息发送的一方公布公钥至消息接收方,这里由乙方将公钥公布给甲方。

完成这两步操作后,甲方就可以向乙方发送加密消息。

国密算法——SM2

SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法。

实例:非对称加密网络应用

目前,非对称加密算法(主要是RSA算法)主要应用于B2C、B2B等多种电子商务平台。但非对称加密算法并不直接对网络数据进行加密/解密,而是用于交换对称加密算法的秘密密钥。最终使用对称加密算法进行真正的加密/解密。

带密钥的消息摘要算法——数字签名算法

数字签名算法可以看做是一种带有密钥的消息摘要算法,并且这种密钥包含了公钥和私钥。也就是说,数字签名算法是非对称加密算法和消息摘要算法的结合体。

数字签名算法是公钥基础设施(Public Key Infrastructure,PKI)以及许多网络安全机制(SSL/TLS、VPN等)的基础。

数字签名算法要求能够验证数据完整性、认证数据来源,并起到抗否认的作用。这3点与OSI参考模型中的数据完整性服务、认证(鉴别)服务和抗否认性服务相对应。

消息摘要算法是验证数据完整性的最佳算法,因此,该算法成为数字签名算法中的必要组成部分。

数字签名算法在实际运用时,通常是先使用消息摘要算法对原始消息做摘要处理,然后再使用私钥对摘要值做签名(加密)处理;验证签名时,则使用公钥解密并验证消息的摘要值。

经典数字签名算法——RSA

RSA数字签名算法主要分为MD系列和SHA系列两大类。MD系列主要包括MD2withRSA和MD5withRSA等数字签名算法;SHA系列主要包括SHA1withRSA、SHA224withRSA、SHA256withRSA、SHA384withRSA和SHA512withRSA等数字签名算法。

签名:

// 实例化Signature
Signature signature = Signature.getInstance("MD5withRSA");
// 初始化Signature为签名
signature.initSign(privateKey);
// 更新
signature.update(data);
// 签名
byte[] sign = signature.sign();

验签:

// 实例化Signature
Signature signature = Signature.getInstance("MD5withRSA");
// 初始化Signature为验签
signature.initVerify(publicKey);
// 更新
signature.update(data);
// 验证
boolean status = signature.verify(sign);

数字签名标准算法——DSA

RSA作为经典数字签名算法,很快就成了数字签名算法的研究对象,并逐步转为标准——DSS,并形成了DSA算法,这为后续数字签名算法的提出奠定了基础,如ECDSA(椭圆曲线数字签名算法)。

1991年,美国国家标准技术协会公布了数字签名标准(Digital Signature Standard,DSS),于1994年正式生效,并作为美国联邦信息处理标准。DSS本质上是ElGamal数字签名算法,DSS使用的算法称为数字签名算法(Digital Signature Algorithm,DSA)。

DSA算法与RSA算法都是数字证书中不可或缺的两种算法。两者不同的是,DSA算法仅包含数字签名算法,使用DSA算法的数字证书无法进行加密通信,而RSA算法既包含加密/解密算法,同时兼有数字签名算法。

与RSA数字签名算法实现相比,DSA算法仅支持SHA系列的消息摘要算法。

Java 8支持的DSA签名算法包括SHA1withDSA、NONEwithDSA、SHA224withDSA、SHA256withDSA。Bouncy Castle支持多种数字签名算法,除了Java 8已经支持的算法外,SHA384WITHDSA、SHA512WITHDSA、SHA3-224WITHDSA、SHA3-256WITHDSA、SHA3-384WITHDSA、SHA3-512WITHDSA等。

椭圆曲线数字签名算法——ECDSA

ECDSA(Elliptic Curve Digital Signature Algorithm,椭圆曲线数字签名算法)于1999年作为ANSI标准,并于2000年成为IEEE和NIST的标准。

ECDSA算法相对于传统签名算法具有速度快、强度高、签名短等优点,其用途也越来越广泛。微软操作系统的25位的产品密钥中就使用了椭圆曲线签名算法,产品密钥就是签名的十六进制串表示形式。

Java 8支持的ECDSA签名算法包括NONEwithECDSA、SHA1withECDSA、SHA224withECDSA、SHA256withECDSA、SHA384withECDSA、SHA512withECDSA。Bouncy Castle支持多种数字签名算法,除了Java 8已经支持的算法外,还支持RIPEMD160withECDSA算法。

国密数字签名算法——SM2

Bouncy Castle支持SHA256WITHSM2、SM3WITHSM2两种SM2签名算法。

KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
EncodedKeySpec spec = new PKCS8EncodedKeySpec(/* byte[] Encoded Private Key */);
PrivateKey privateKey = keyFactory.generatePrivate(spec);
// SM3withSM2是SM3WITHSM2的别名
Signature signature = Signature.getInstance("SM3withSM2");
// 初始化为签名
signature.initSign(privateKey);
signature.update(/* byte[] data */);
// 签名数据
byte[] signResult = signature.sign();

带有数字签名的加密网络应用

数字签名算法是公钥基础设施(Public Key Infrastructure,PKI)以及许多网络安全机制(SSL/TLS、VPN等)的基础。数字签名算法包含签名和验证两项操作,遵循“私钥签名,公钥验证”的签名/验证方式,签名时需要使用私钥和待签名数据,其核心算法主要是消息摘要算法。因此,我们可以把数字签名算法近似看成是一种附加了公钥和私钥的消息摘要算法。

数字签名算法主要包括RSA、DSA和ECDSA共3种算法。其中,RSA算法源于整数因子分解问题,DSA和ECDSA算法源于离散对数问题。

RSA算法是数字签名算法中的经典,主要可以分为MD系列和SHA系列两大类。

RSA算法是目前应用最为广泛的非对称加密算法和数字签名算法,在电子商务和产品验证方面均有使用。

DSA算法是继RSA算法后出现的基于DSS的数字签名算法,旨在形成数字签名标准。并且,DSA算法本身不包含任何消息摘要算法。DSA算法主要为后续数字签名算法的形成奠定基础。

应用参考《基于签名验签的认证方案》

数字证书

数字证书集合了多种密码学算法:自身带有公钥信息,可完成相应的加密/解密操作;同时,还带有数字签名,可鉴别消息来源;且自身带有消息摘要信息,可验证证书的完整性;由于证书本身含有用户身份信息,因而具有认证性。

数字证书具备常规加密/解密必要的信息,包含签名算法,可用于网络数据加密/解密交互,标识网络用户(计算机)身份。数字证书为发布公钥提供了一种简便的途径,其数字证书则成为加密算法以及公钥的载体。依靠数字证书,我们可以构建一个简单的加密网络应用平台。

实际上,数字证书是采用了公钥基础设施(Public Key Infrastructure,PKI),使用了相应的加密算法确保网络应用的安全性:

  • 非对称加密算法用于对数据进行加密/解密操作,确保数据的机密性。
  • 数字签名算法用于对数据进行签名/验证操作,确保数据的完整性和抗否认性。
  • 消息摘要算法用于对数字证书本身做摘要处理,确保数字证书完整性。

所有证书都符合公钥基础设施(PKI)制定的ITU-T X509国际标准(X.509标准)。所有证书都符合公钥基础设施(PKI)制定的ITU-T X509国际标准(X.509标准)。

证书管理

参考《基于证书的双向认证技术方案介绍》

证书文件

参考《基于证书的双向认证技术方案介绍》

应用实例

数字证书常与传输层SSL/TLS协议共同构建应用层HTTPS协议,确保网络交互安全。目前,各大电子商务网站均使用HTTPS协议,确保网络交易安全。各种Web Service为确保数据交互的安全性,通常使用HTTPS协议加强系统安全性。

参考《基于证书的双向认证技术方案介绍》

安全协议

HTTPS协议和SSL/TLS协议分属TCP/IP参考模型中的应用层和传输层。简单地说,HTTPS就是附加了SSL/TLS协议的HTTP协议,就是HTTP安全版。HTTPS协议为数字证书提供了最佳的应用环境。

HTTPS协议

HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)协议是Web上最为常用的安全访问协议。相比于SSL/TLS协议,HTTPS协议我们更为熟悉。

SSL/TLS协议

SSL/TLS协议包含两个协议:SSL和TLS。

  • SSL(Secure Socket Layer,安全套接字层):由Netscape(网景)公司研发,位于TCP/IP参考模型中的网络传输层,作为为网络通信提供安全及数据完整性的一种安全协议。
  • TLS(Transport Layer Security,传输层安全):基于SSL协议之上的通用化协议,它同样位于TCP/IP参考模型中的网络传输层,作为SSL协议的继任者,成为下一代网络安全性和数据完整性安全协议。详见文档RFC 2246(http://www.ietf.org/rfc/rfc2246.txt)。

单向认证服务

双向认证服务

双向认证服务与单向认证服务的主要差别在于双向认证服务增加了客户证书验证环节。这使得消息收发双方可以通过证书相互验证对方的身份,达到双向认证的作用。

双向认证服务需要根证书、服务器证书和客户证书共3项证书。

案例

《基于签名验签的认证方案》
《基于证书的双向认证技术方案介绍》

参考文档

如果确认您的机器上支持哪些加密算法:

import java.security.MessageDigest;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.Security;
import java.security.Signature;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.Mac;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class ShowHashAlgorithms {

// 加载 Bouncy Castle 算法实现
static {
    if (Security.getProvider("BC") == null) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

private static final void showHashAlgorithms(Provider prov, Class<?> typeClass) {
    String type = typeClass.getSimpleName();

    List<Service> algos = new ArrayList<>();

    Set<Service> services = prov.getServices();
    for (Service service : services) {
        if (service.getType().equalsIgnoreCase(type)) {
            algos.add(service);
        }
    }

    if (!algos.isEmpty()) {
        System.out.printf(" --- Provider %s, version %.2f --- %n", prov.getName(), prov.getVersion());
        for (Service service : algos) {
            String algo = service.getAlgorithm();
            System.out.printf("Algorithm name: \"%s\"%n", algo);


        }
    }

    // --- find aliases (inefficiently)
    Set<Object> keys = prov.keySet();
    for (Object key : keys) {
        final String prefix = "Alg.Alias." + type + ".";
        if (key.toString().startsWith(prefix)) {
            String value = prov.get(key.toString()).toString();
            System.out.printf("Alias: \"%s\" -> \"%s\"%n",
                    key.toString().substring(prefix.length()),
                    value);
        }
    }
}

public static void main(String[] args) {
    Provider[] providers = Security.getProviders();
    for (Provider provider : providers) {
        // 打印支持的摘要算法
        //showHashAlgorithms(provider, MessageDigest.class);
        // 打印支持的带密钥的摘要算法
        //showHashAlgorithms(provider, Mac.class);
        // 打印支持的加密算法
        //showHashAlgorithms(provider, Cipher.class);
        // 打印支持的签名算法
        showHashAlgorithms(provider, Signature.class);
    }
}

}