哈Ha! 我向您介绍Jakob Jenkov撰写的文章“ Java密码学”的翻译。
该出版物是第一篇Java密码学文章的翻译,该文章是针对那些想学习Java密码学基础知识的初学者的。
目录:
- Java密码学
- 密码
- 留言摘要
- Mac电脑
- 签章
- 密钥对
- 密钥生成器
- 密钥对生成器
- 密钥库
- 按键工具
- 证明书
- 证书工厂
- 证书路径
Java密码学
Java密码学API提供了对Java中的数据进行加密和解密以及管理密钥,签名和认证(认证)消息,计算密码哈希等等的功能。
本文介绍了如何使用Java密码API执行需要安全加密的各种任务的基础知识。
本文不介绍密码学理论的基础。 您将不得不在其他地方看到此信息。
Java密码学扩展
Java密码学API由所谓的Java密码学扩展 (JCE)提供。 JCE长期以来一直是Java平台的一部分。 最初,由于美国对加密技术的出口限制,JCE与Java分离。 因此,最强的加密算法未包含在标准Java平台中。 如果您的公司位于美国,则可以应用这些更强大的加密算法,但是在其他情况下,您将不得不使用较弱的算法或实现自己的加密算法并将其连接到JCE。
自2017年以来,美国出口加密算法的规则已大大放宽,在世界上大多数地区,您都可以通过Java JCE使用国际加密标准。
Java密码体系结构
Java密码体系结构(JCA)是Java内部密码API设计的名称。 JCA是围绕几个核心类和通用接口构建的。 这些接口的实际功能由供应商提供。 因此,您可以使用Cipher类来加密和解密某些数据,但是密码(加密算法)的具体实现取决于所使用的特定提供程序。
您也可以实现并连接自己的提供程序,但是您必须小心谨慎。 正确实现没有安全漏洞的加密非常困难! 如果您不知道自己在做什么,最好使用内置Java提供程序或使用诸如Bouncy Castle之类的受信任提供程序。
主要类别和介面
Java密码学API包含以下Java软件包:
- java.security
- java.security.cert
- java.security.spec
- java.security.interfaces
- javax.crypto
- javax.crypto.spec
- javax.crypto.interfaces
这些软件包的主要类和接口:
- 提供者
- 安全随机
- 密码
- 留言摘要
- 签章
- Mac电脑
- 算法参数
- AlgorithmParameterGenerator
- 关键工厂
- SecretKeyFactory
- 密钥对生成器
- 密钥生成器
- 关键协议
- 密钥库
- 证书工厂
- CertPathBuilder
- CertPathValidator
- 证书库
提供者
Provider类(java.security.Provider)是Java crypto API中的中心类。 为了使用Java crypto API,您需要安装密码提供程序。 Java SDK带有自己的加密提供程序。 除非您明确设置密码提供程序,否则将使用默认提供程序。 但是,此密码提供程序可能不支持您要使用的加密算法。 因此,您可能必须安装自己的加密提供程序。
Java加密API最受欢迎的加密提供程序之一称为Bouncy Castle。 这是一个示例,其中BouncyCastleProvider设置为加密提供程序:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } }
密码
Cipher类(javax.crypto.Cipher)表示一种加密算法。 密码可用于加密和解密数据。 以下各节将对Cipher类进行更详细的说明,并在下面进行简要说明。
创建使用AES加密算法供内部使用的密码类的实例:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher.getInstance(...)方法接受一个字符串,该字符串确定要使用的加密算法以及其他一些算法参数。
在上面的示例中:
- AES-加密算法
- CBC是AES算法可以工作的一种模式。
- PKCS5Padding是AES算法应如何处理数据的最后字节进行加密的方式。 这到底是什么意思,请整体上看密码手册,而不是本文。
密码初始化
在使用密码实例之前,必须对其进行初始化。 通过调用init()方法初始化密码实例。 init()方法采用两个参数:
第一个参数指示密码实例的操作模式:加密或解密数据。 第二个参数指示它们用于加密或解密数据的密钥。
一个例子:
byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key);
请注意,此示例中的密钥创建方法是不安全的,不应在实践中使用。 以下各节中的本文将说明如何更安全地创建密钥。
要初始化密码实例以解密数据,必须使用Cipher.DECRYPT_MODE,例如:
cipher.init(Cipher.DECRYPT_MODE, key);
数据加密或解密
初始化密码后,您可以通过调用update()或doFinal()方法开始加密或解密数据。 如果要加密或解密一条数据,则使用update()方法。 当您加密最后一条数据或传递给doFinal()的数据块是用于加密的单个数据集时,将调用doFinal()方法。
使用doFinal()方法进行数据加密的示例:
byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText);
要解密数据,您需要将密文(数据)传递给doFinal()或doUpdate()方法。
按键
要加密或解密数据,您需要一个密钥。 密钥有两种,具体取决于使用哪种加密算法:
对称密钥用于对称加密算法。 对称加密算法使用相同的密钥进行加密和解密。
非对称密钥用于非对称加密算法。 非对称加密算法将一个密钥用于加密,将另一个密钥用于解密。 公钥和私钥加密算法是非对称加密算法的示例。
不知何故,需要解密数据的一方必须知道解密数据所需的密钥。 如果解密者不是数据加密的一方,则双方必须就密钥达成一致或交换密钥。 这称为密钥交换。
密钥安全
密钥必须很难猜测,以使攻击者无法轻易获取加密密钥。 在上一节有关Cipher类的示例中,使用了一个非常简单的硬编码密钥。 实际上,这是不值得做的。 如果各方的密钥很容易猜到,那么攻击者将很容易解密加密的数据,并有可能自行创建虚假消息。 制作难以猜测的密钥非常重要。 因此,密钥应由随机字节组成。 随机字节越多,猜测就越困难,因为存在更多可能的组合。
密钥生成
要生成随机加密密钥,可以使用Java KeyGenerator类。 以下几章将更详细地描述KeyGenerator,这是在此使用的一个小示例:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey();
可以将生成的SecretKey实例传递给Cipher.init()方法,例如:
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
密钥对生成
非对称加密算法使用由公共密钥和私有密钥组成的密钥对来加密和解密数据。 要创建非对称密钥对,可以使用KeyPairGenerator(java.security.KeyPairGenerator)。 以下几章将更详细地描述KeyPairGenerator,以下是使用Java KeyPairGenerator的简单示例:
SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair();
密钥库
Java KeyStore是可以包含密钥的数据库。 Java KeyStore由KeyStore类(java.security.KeyStore)表示。 密钥库可以包含以下类型的密钥:
私钥和公钥用于非对称加密。 公钥可以具有关联的证书。 证书是证明声称拥有公钥的个人,组织或设备的身份的文档。
证书通常由依赖方进行数字签名作为证据。
私钥用于对称加密,KeyStore类非常复杂,这就是为什么稍后在Java KeyStore的单独一章中对其进行详细描述的原因。
Java Keytool是可以与Java KeyStore文件一起使用的命令行工具。 Keytool可以在KeyStore文件中生成密钥对,导出证书并将证书导入KeyStore和其他一些功能。 Keytool随附Java安装。 稍后将在Java Keytool的单独一章中更详细地描述Keytool。
留言文摘
当您从另一端收到加密数据时,可以确定没有人在去往您的途中更改过加密数据吗?
通常,解决方案是先对数据中的消息摘要进行加密,然后再对数据和消息摘要进行加密,然后通过网络发送。 消息摘要是基于消息数据计算的哈希值。 如果加密数据中的至少一个字节被更改,则从该数据计算出的消息摘要也将更改。
收到加密的数据时,将其解密,从它们中计算出消息摘要,然后将计算出的消息摘要与随加密数据一起发送的消息摘要进行比较。 如果两个消息摘要相同,则很有可能(但不是100%)数据没有被更改。
Java MessageDigest(java.security.MessageDigest)可用于计算消息摘要。 若要创建MessageDigest的实例,将调用MessageDigest.getInstance()方法。 有几种不同的消息摘要算法。 您需要指定创建MessageDigest实例时要使用的算法。 在Java MessageDigest一章中将更详细地描述使用MessageDigest。
MessageDigest类的简要介绍:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
本示例创建一个MessageDigest实例,该实例使用SHA-256内部加密哈希算法来计算消息摘要。
要计算某些数据的消息摘要,请调用update()或digest()方法。 可以多次调用update()方法,并在对象内部更新消息摘要。 传递完要包含在消息摘要中的所有数据后,可以调用digest()并检索消息摘要摘要。
多次调用update() ,然后调用摘要()的示例:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest();
您还可以调用一次digest() ,传递所有数据以计算消息的摘要。 一个例子:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1);
消息验证码(MAC)
Java Mac类用于从消息创建MAC(消息身份验证代码)。 MAC与消息摘要类似,但是使用其他密钥来加密消息摘要。 仅具有源数据和密钥,您可以检查MAC。 因此,与消息摘要相比,MAC是一种保护数据块免受修改的更安全的方法。 Mac类在Java Mac章节中有更详细的描述,然后进行简要介绍。
通过调用Mac.getInstance()方法并传递要用作参数的算法名称来创建Java Mac实例。 看起来是这样的:
Mac mac = Mac.getInstance("HmacSHA256");
从数据创建MAC之前,必须使用密钥初始化Mac实例。 这是使用密钥初始化Mac实例的示例:
byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key);
初始化Mac实例后,您可以通过调用update()和doFinal()方法从数据中计算MAC。 如果拥有用于计算MAC的所有数据,则可以立即调用doFinal()方法。 看起来是这样的:
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); byte[] data2 = "0123456789".getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal();
签章
Signature类(java.security.Signature)用于对数据进行数字签名。 对数据签名后,将从该数据创建数字签名。 因此,签名与数据分离。
通过从数据创建消息摘要(哈希)并使用必须对数据进行签名的设备,个人或组织的私钥对该消息摘要进行加密,可以创建数字签名。 加密消息的摘要称为数字签名。
要创建Signature的实例,请调用Signature.getInstance(...)方法:
Signature signature = Signature.getInstance("SHA256WithDSA");
数据签名
要对数据进行签名,必须通过调用initSign(...)方法并传递私钥对数据进行签名,从而以签名方式初始化签名实例。 在签名模式下初始化签名实例的示例:
signature.initSign(keyPair.getPrivate(), secureRandom);
初始化签名实例后,可以使用它对数据进行签名。 这是通过调用update()方法并将签名数据作为参数传递来完成的。 您可以多次调用update()方法来补充用于创建签名的数据。 将所有数据传递给update()方法之后,将调用sign()方法以获得数字签名。 看起来是这样的:
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign();
签名验证
要验证签名,您需要在验证模式下通过调用initVerify(...)方法来初始化签名实例,并将用于验证签名的公钥作为参数传递。 在验证模式下初始化签名实例的示例如下所示:
Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic());
在验证模式下初始化后,已签名的数据将传输到update()方法。 取决于是否可以验证签名,对verify()方法的调用将返回true或false 。 这是签名验证:
byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature);
完整的签名和验证示例
SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified);