Java密码学

Java Cryptography API 使您能够 在Java 中加密和解密数据,以及管理密钥、签署和验证消息、计算加密哈希等等。密码学一词通常缩写为crypto,因此有时您会看到对 Java 密码学的引用,而不是 Java 密码学。这两个术语虽然指的是同一个主题。

在此 Java 密码学教程中,我将解释如何使用 Java 密码学 API 执行安全加密所需的不同任务的基础知识。

本 Java 密码学教程不会涵盖底层密码学理论。您现在必须到别处寻找。

Java 加密扩展

Java 加密 API 由官方称为Java Cryptography Extension的内容提供。Java Cryptography Extension 有时也通过缩写JCE引用。

Java Cryptography Extension 长期以来一直是 Java 平台的一部分。JCE 最初与 Java 分开,因为美国对加密技术有一些出口限制。因此,最强的加密算法没有包含在标准 Java 平台中。如果您是美国境内的公司,您可以获得这些更强大的 Java JCE 加密算法,但世界其他地方必须使用较弱的算法(或实现他们自己的加密算法并插入 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

这些包的核心类和接口是:

  • Provider
  • SecureRandom
  • Cipher
  • MessageDigest
  • Signature
  • Mac
  • AlgorithmParameters
  • AlgorithmParameterGenerator
  • KeyFactory
  • SecretKeyFactory
  • KeyPairGenerator
  • KeyGenerator
  • KeyAgreement
  • KeyStore
  • CertificateFactory
  • CertPathBuilder
  • CertPathValidator
  • CertStore
  • 供应商
  • 安全随机
  • 密码
  • 信息摘要
  • 签名
  • 苹果电脑
  • 算法参数
  • 算法参数生成器
  • 钥匙工厂
  • 秘钥工厂
  • 密钥对生成器
  • 密钥生成器
  • 密钥协议
  • 密钥库
  • 证书工厂
  • CertPathBuilder
  • 证书路径验证器
  • 证书库

本 Java 密码学教程的其余部分涵盖了这些类中最常用的类。

供应商

Provider)java.security.Provider类是 Java 密码学 API 中的中心类。为了使用 Java 加密 API,您需要一个Provider集合。Java SDK 带有自己的加密提供程序。如果您未设置显式加密提供程序,则使用 Java SDK 默认提供程序。但是,此提供商可能不支持您要使用的加密算法。因此,您可能必须设置自己的加密提供程序。

Java 加密 API 最流行的加密提供程序之一称为 Bouncy Castle。这是一个设置 a 的示例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类表示密码算法。密码可用于加密和解密数据。该类在Java CipherCipher类的文本中有更详细的解释,但我将在以下部分中对该类进行简要介绍。 Cipher

以下是创建 JavaCipher实例的方法:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

本示例创建一个Cipher内部使用 AES 加密算法的实例。

Cipher.getInstance(...)方法采用一个字符串来标识要使用的加密算法,以及该算法的一些其他配置。在上面的例子中,这CBC 部分是 AES 算法可以工作的模式。这PKCS5Padding部分是 AES 算法应该如何处理要加密的数据的最后字节,如果数据不符合 64 位或 128 位块大小边界。这究竟意味着什么,属于一般密码学教程,而不是 Java 密码学 API 教程。

初始化密码

Cipher可以使用实例之前,必须对其进行初始化。Cipher 您通过调用实例的方法来初始化实例init()。该init()方法有两个参数:

  • 加密/解密密码模式
  • 钥匙

第一个参数指定Cipher实例是否应该加密或解密数据。第二个参数指定用于加密或解密数据的密钥。

这是一个 JavaCipher.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);

请注意,此示例中创建密钥的方式不安全,不应在实践中使用。本 Java 密码学教程将在后面的部分中介绍如何更安全地创建密钥。

要初始化Cipher实例以解密数据,您必须使用Cipher.DECRYPT_MODE,如下所示:

cipher.init(Cipher.DECRYPT_MODE, key);

加密或解密数据

Cipher正确初始化 后,您可以开始加密或解密数据。您可以通过调用Cipher update()doFinal()方法来实现。

update()如果您正在加密或解密较大数据块的一部分,则使用 该方法。doFinal()当您加密大块数据的最后一部分时,或者如果您传递给的块doFinal()代表要加密的完整数据块, 则会调用该方法。

doFinal()这是使用该方法 加密某些数据的示例

byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(plainText);

要解密数据,您可以将密文(加密数据)传递给doFinal()or doUpdate()方法。

钥匙Keys

要加密或解密数据,您需要一个密钥。有两种类型的密钥 – 取决于您使用的加密算法类型:

  • 对称密钥
  • 非对称密钥

对称密钥用于对称加密算法。对称加密算法使用相同的密钥进行加密和解密。

非对称密钥用于非对称加密算法。非对称加密算法使用一个密钥进行加密,另一个密钥进行解密。公钥-私钥加密算法是非对称加密算法的例子。

需要解密数据的一方需要知道解密数据所需的密钥。如果解密数据的一方与加密数据的一方不同,则这两方需要以某种方式就密钥达成一致,或交换密钥。这称为密钥交换

密钥安全

密钥应该难以猜测,这样攻击者就无法轻易猜出加密密钥。上一节中有关Cipher该类的示例使用了一个非常简单的硬编码密钥。这在实践中不是一个好主意。如果他们的密钥很容易被猜到,攻击者就很容易解密加密数据并可能自己创建假消息。

使密钥难以猜测很重要。因此,密钥应该由随机字节组成。越随机越好,字节越多越难猜,因为有更多可能的组合。

生成密钥

您可以使用 JavaKeyGenerator类生成更多随机加密密钥。在有关Java KeyGeneratorKeyGenerator的文本中有更详细的介绍 ,但我将在此处向您展示如何使用它的示例。

这是一个 JavaKeyGenerator示例:

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)。Java KeyPairGenerator 教程中对此KeyPairGenerator进行了更详细的介绍,但这里是一个简单的 Java示例: KeyPairGenerator

SecureRandom secureRandom = new SecureRandom();

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");

KeyPair keyPair = keyPairGenerator.generateKeyPair();

密钥库

Java KeyStore是一个可以包含密钥的数据库。Java KeyStore 由 KeyStorejava.security.KeyStore) 类表示。AKeyStore可以持有以下类型的密钥:

  • 私钥
  • 公钥+证书
  • 秘钥

私钥和公钥用于非对称加密。公钥可以有关联的证书。证书是一种文件,用于验证声称拥有公钥的个人、组织或设备的身份。证书通常由验证方进行数字签名作为证明。

密钥用于对称加密。

该类KeyStore非常高级,因此在其自己的 Java KeyStore 教程中对其进行了更详细的描述。

钥匙工具Keytool

Java Keytool 是一个命令行工具,可以处理 Java KeyStore 文件。Keytool 可以将密钥对生成到 KeyStore 文件中、从 KeyStore 导出证书以及将证书导入到 KeyStore 以及其他几个功能。

Keytool 随 Java 安装一起提供。Keytool 在有关Java Keytool的教程中有更详细的描述。

信息摘要

当你从别人那里收到一些加密数据时,你怎么知道在给你的路上没有人修改过加密数据?

一个常见的解决方案是在加密之前从数据中计算出消息摘要,然后加密数据和消息摘要并通过网络发送。消息摘要是根据消息数据计算的散列值。如果加密数据中的一个字节发生变化,则根据该数据计算出的消息摘要也会发生变化。

当收到加密数据时,您对其进行解密并从中计算出消息摘要,并将计算出的消息摘要与随加密​​数据一起发送的消息摘要进行比较。如果两个消息摘要相同,则很可能(但不是 100% 保证)数据未被修改。

您可以使用 Java MessageDigestjava.security.MessageDigest) 来计算消息摘要。您调用该MessageDigest.getInstance()方法来创建一个MessageDigest实例。有几种不同的消息摘要算法可用。MessageDigest您需要在创建实例时告知要使用哪种算法 。这在Java MessageDigest教程中有更详细的介绍 。这是该课程的简短介绍 MessageDigest

这是创建示例的MessageDigest示例:

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

此示例创建MessageDigest在内部使用 SHA-256 加密哈希算法来计算消息摘要的实例。

为了计算某些数据的消息摘要,您调用update()ordigest() 方法。

update()方法可以多次调用,消息摘要在内部更新。当您传递了所有要包含在消息摘要中的数据后,您调用digest()并获取结果消息摘要数据。update()以下是多次调用后调用的示例digest()

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

JavaMac类用于根据消息创建MAC。术语 MAC 是消息验证代码的缩写。MAC 类似于消息摘要,但使用额外的密钥来加密消息摘要。只有同时拥有原始数据和密钥,才能验证 MAC。因此,MAC 是一种比消息摘要更安全的保护数据块不被修改的方法。该类在Java MacMac教程中有更详细的描述,但下面是一个简短的介绍。

Mac您可以通过调用该方法 创建一个 Java实例Mac.getInstance(),将要使用的算法名称作为参数传递。这是它的样子:

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};
字符串算法 = "RawBytes";
SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm);

mac.init(key);

初始化实例后,Mac您可以通过调用 update()anddoFinal()方法从数据中计算 MAC。如果您拥有用于计算 MAC 的所有数据,则可以doFinal()立即调用该方法。这是它的样子:

byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");

byte[] macBytes = mac.doFinal(data);

如果您只能访问单独块中的数据,请update()多次调用数据,然后调用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 = Signature.getInstance("SHA256WithDSA");

签名数据

要签署数据,您必须Signature以签名模式初始化实例。为此,您可以调用initSign(...)传递私钥的方法来对数据进行签名。以下是如何在签名模式下初始化Signature实例:

signature.initSign(keyPair.getPrivate(), secureRandom);

一旦Signature实例被初始化,它就可以用来签署数据。您可以通过调用 update()将数据传递给 sign 作为参数来实现。update()您可以多次调用该方法,并在创建签名时包含更多数据。当所有数据都已传递给update()方法时,您可以调用该sign()方法来获取数字签名。这是它的样子:

byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature2.update(data2);

boolean verified = signature2.verify(digitalSignature);

验证签名

要验证签名,您必须将Signature实例初始化为验证模式。这是通过调用将initVerify(...)公钥作为参数传递给用于验证签名的方法来完成的。下面是现在初始化一个Signature实例进入验证模式的样子:

Signature signature = Signature.getInstance("SHA256WithDSA");

signature.initVerify(keyPair.getPublic());

一旦初始化为验证模式,您就可以update()使用签名正在签名的数据调用该方法,并以verify()返回truefalse 取决于签名是否可以验证的调用结束。以下是验证签名的方式:

byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature2.update(data2);

boolean verified = signature2.verify(digitalSignature);

完整签名和验证示例

以下是使用Signature类创建和验证数字签名的完整示例:

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);