Java密码学
Java密码学API使我们可以使用Java加密和解密数据,以及管理密钥,签名和验证消息,计算加密哈希等等。术语"加密"通常缩写为"加密",因此有时我们会看到对Java加密的引用,而不是Java密码学。但是,这两个术语指的是同一主题。
在本Java密码学教程中,我将解释如何使用Java密码学API来执行安全加密所需的不同任务的基础知识。
本Java密码学教程不会涵盖基础密码学理论。我们现在必须为此寻找其他地方。
Java密码学扩展
Java加密API由正式称为Java加密扩展的Java提供。有时也通过缩写JCE来引用Java密码学扩展。
Java密码术扩展很久以来一直是Java平台的一部分。 JCE最初与Java分开是因为美国对加密技术有一些出口限制。因此,最强的加密算法未包含在标准Java平台中。如果我们是美国境内的公司,则可以为Java JCE获得这些更强大的加密算法,但世界其他地方必须使用较弱的算法(或者实现自己的加密算法并插入JCE)。
如今(2016年),美国加密出口规则已经放宽了很多。因此,世界上大多数地区都可以通过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
这些类中最常用的类涵盖了本Java密码学教程的其余部分。
Provider
Provider(java.security.Provider)类是Java加密API中的中心类。为了使用Java crypto API,我们需要一个" Provider"集。 Java SDK带有自己的加密提供程序。如果未设置显式密码提供程序,则使用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)类代表一种加密算法。密码可用于加密和解密数据。 Java Cipher类的文本中对Cipher
类进行了更详细的说明,但是在以下各节中,我将对Cipher
类进行简要介绍。
以下是创建JavaCipher
实例的方法:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
本示例创建一个Cipher
实例,该实例在内部使用AES加密算法。
" Cipher.getInstance(...)"方法采用一个String来标识要使用的加密算法,以及该算法的其他一些配置。在上面的示例中," CBC"部分是AES算法可以使用的模式。" PKCS5Padding"部分是AES算法如何处理数据的最后字节(如果数据不与64对齐)。位或者128位块大小边界。确切的说,这通常属于有关密码学的教程,而不是有关Java密码学API的教程。
初始化密码
在可以使用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``update()
或者doFinal()
方法来实现。
如果要加密或者解密较大数据块的一部分,则使用" update()"方法。当我们加密大块数据的最后一部分时,或者如果我们传递给doFinal()
的块代表要加密的完整数据块,则将调用doFinal()
方法。
这是使用doFinal()方法加密某些数据的示例
byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText);
要解密数据,我们需要将密文(加密的数据)传递到doFinal()或者doUpdate()方法中。
按键
要加密或者解密数据,我们需要一个密钥。有两种类型的密钥,具体取决于我们使用的加密算法类型:
- 对称键
- 非对称密钥
对称密钥用于对称加密算法。对称加密算法使用相同的密钥进行加密和解密。
非对称密钥用于非对称加密算法。非对称加密算法将一个密钥用于加密,将另一个密钥用于解密。公钥私钥加密算法是非对称加密算法的示例。
需要解密数据的一方以某种方式需要知道解密数据所需的密钥。如果解密数据的一方与加密数据的一方不同,那么这两个当事方就需要以某种方式商定密钥或者交换密钥。这称为密钥交换。
密钥安全
密钥应该很难猜到,因此攻击者无法轻易猜出加密密钥。上一节中有关Cipher
类的示例使用了一个非常简单的硬编码键。实际上这不是一个好主意。如果他们的密钥很容易猜到,那么攻击者很容易解密加密的数据并可能自己创建虚假消息。
重要的是,使钥匙难以猜测。因此,密钥应由随机字节组成。随机性越好,字节越多,则越难猜测,因为存在更多可能的组合。
产生金钥
我们可以使用JavaKeyGenerator
类来生成更多随机加密密钥。关于Java KeyGenerator的文本中将更详细地介绍KeyGenerator
,但在这里我将向我们展示如何使用它的示例。
这是一个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由KeyStore(java.security.KeyStore)类表示。 " KeyStore"可以保存以下类型的密钥:
- 私钥
- 公钥+证书
- 秘钥
私钥和公钥用于非对称加密。公钥可以具有关联的证书。证书是证明声称拥有公钥的个人,组织或者设备的身份的文档。证书通常由验证方进行数字签名以作为证明。
秘密密钥用于对称加密。
KeyStore类非常高级,因此在其自己的Java KeyStore教程中对其进行了详细描述。
按键工具
Java Keytool是可以与Java KeyStore文件一起使用的命令行工具。 Keytool可以将密钥对生成到KeyStore文件中,可以从中导出证书,也可以将证书导入到KeyStore和其他几个函数中。
Keytool随Java安装一起提供。关于Java Keytool的教程中对Keytool进行了更详细的描述。
信息摘要
当我们从其他人那里收到一些加密数据时,我们怎么知道没有人在去往途中修改过加密数据?
一种常见的解决方案是在对数据进行加密之前,先从数据中计算出消息摘要,然后对数据和消息摘要进行加密,然后通过网络进行发送。消息摘要是根据消息数据计算出的哈希值。如果更改了加密数据中的字节,则从该数据计算出的消息摘要也将更改。
接收加密的数据时,可以对其解密并从中计算消息摘要,然后将计算出的消息摘要与随加密数据一起发送的消息摘要进行比较。如果两个消息摘要相同,则很有可能未修改数据(但不是100%保证)。
我们可以使用Java MessageDigest(java.security.MessageDigest)计算消息摘要。我们可以调用MessageDigest.getInstance()方法来创建MessageDigest实例。有几种不同的消息摘要算法可用。我们需要告诉我们在创建MessageDigest
实例时要使用哪种算法。 Java MessageDigest教程中对此进行了更详细的介绍。这是MessageDigest类的简短介绍。
这是创建" MessageDigest"示例的示例:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
本示例创建" MessageDigest"实例,该实例在内部使用SHA-256密码哈希算法来计算消息摘要。
为了计算某些数据的消息摘要,请调用update()
或者digest()
方法。
可以多次调用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是一种保护数据块免受修改的更安全的方法。 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[] 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.getInstance("SHA256WithDSA");
签名数据
要签名数据,我们必须在签名模式下初始化Signature
实例。为此,我们可以调用initSign(...)
方法,并传递用于签名数据的私钥。这是在签名模式下初始化Signature
实例的方式:
signature.initSign(keyPair.getPrivate(), secureRandom);
一旦Signature
实例被初始化,它就可以被用来对数据签名。我们可以通过调用update()
来传递数据,以将其作为参数签名。我们可以多次调用update()
方法,并在创建签名时包含更多数据。当所有数据都传递给update()方法后,我们可以调用sign()方法来获取数字签名。看起来是这样的:
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign();
验证签名
要验证签名,我们必须将Signature
实例初始化为验证模式。这是通过调用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);
完整签名和验证示例
这是使用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);