admin管理员组文章数量:1651261
在本章中,我们将探讨 Java 为保护应用程序之间的通信提供的支持。我们将研究几个主题,包括以下内容:
- 基本加密过程
- 使用密钥库存储密钥和证书
- 向简单的服务器/客户端添加加密
- 使用 TLS\SSL 保护客户端/服务器通信
- 安全散列
安全
有许多与安全相关的术语,当它们第一次遇到时,它们的含义和目的可能会令人生畏。大多数这些术语都适用于网络应用程序。我们将从对其中许多术语的简要概述开始。在本章的后面部分,我们将详细介绍与我们的讨论相关的内容。
大多数安全相关问题的核心是加密。这是使用密钥或一组密钥将需要保护的信息转换为加密形式的过程。加密信息的接收者可以使用一个密钥或一组密钥来解密信息并将其恢复为原始形式。这种技术将防止对信息的未授权访问。
我们将演示对称和非对称加密技术的使用。对称加密使用单个密钥来加密和解密消息。非对称加密使用一对密钥。这些密钥经常存储在一个名为keystore的文件中,我们将对此进行演示。
对称加密通常更快,但要求加密数据的发送方和接收方以安全可靠的方式共享其密钥。对于远程分散的各方来说,这可能是一个问题。非对称加密速度较慢,但它使用公钥和私钥对,正如我们将看到的,简化了密钥的共享。非对称加密是数字证书的一种使能技术,它提供了一种验证文档真实性的方法。
安全商务很常见,对于每天在全球范围内发生的在线交易至关重要。在传输层安全(TLS)和安全套接字层(SSL)的协议,允许在互联网上安全可靠的通信。它是用于在 Internet 上进行大多数交易的超文本传输协议安全( HTTPS )的基础。该协议支持以下内容:
- 服务器和客户端身份验证
- 数据加密
- 数据的完整性
安全散列是一种用于创建证书的技术。甲证书用于验证数据的真实性,并且它使用一个散列值。Java 为这个过程提供了支持,我们将对此进行演示。
让我们首先简要介绍常见的网络安全术语,以提供本章的高级视角。在随后的部分中更详细地探讨了特定术语。
安全通信术语
在处理安全通信时使用了几个术语。这些包括以下内容:
- 认证:这是的过程验证用户或系统
- 授权:这是过程,允许访问受保护资源
- 加密:这是对信息进行编码和随后解码的过程,以保护它免受未经授权的个人的侵害
- 散列算法:这些提供了一种为文档生成唯一值的方法,它们用于支持其他安全技术
- 数字签名:这些提供了一种对文档进行数字身份验证的方法
- 证书:这些通常用作链,它们支持确认委托人和其他参与者的身份
认证和授权是相关的。身份验证是确定一个人或系统是否是他们声称的身份的过程。这通常是使用 ID 和密码来实现的。但是,还有其他身份验证技术(例如智能卡)和生物特征签名(例如指纹或虹膜扫描)。
授权是确定个人或系统可以访问哪些资源的过程。验证一个人是否就是他们所说的人是一回事。确保用户只能访问授权资源是另一回事。
加密已经发展并将继续改进。Java 支持对称和非对称加密技术。该过程从生成密钥开始,密钥通常存储在密钥库中。需要加密或解密数据的应用程序将访问密钥库以检索适当的密钥。密钥库本身需要受到保护,以免被篡改或以其他方式受到损害。
散列是获取数据并返回代表数据的数字的过程。哈希算法执行此操作,并且速度必须很快。然而,如果仅给出散列值,导出原始数据是极其困难的,如果不是不可能的话。这称为单向哈希函数。
这种技术的优点是数据可以与散列值一起发送到接收器。数据未加密,但哈希值使用一组非对称密钥加密。然后接收方可以使用原始散列算法来计算接收数据的散列值。如果这个新的散列值与发送的散列值匹配,那么接收方可以确信数据在传输中没有被修改或损坏。这提供了一种更可靠的数据传输方式,不需要加密,但可以保证数据未被修改。
证书是前一个过程的一部分,它使用散列函数和非对称密钥。甲证书链 提供验证的证书是有效的,假定链的根可以被信任的手段。
加密基础
在本节中,我们将研究 Java 如何支持对称和非对称加密。正如我们将看到的,有多种加密算法可用于这两种技术。
对称加密技术
对称加密使用单个密钥来加密和解密消息。这种类型的加密分为流密码或分组密码。有关这些算法的更多详细信息,请访问https://en.wikipedia/wiki/Symmetric-key_algorithm。提供者提供加密算法的实现,我们经常在它们之间进行选择。
Java 支持的对称算法包括以下算法,其中以位为单位的密钥大小括在括号中:
- AES (128)
- DES (56)
- DESede (168)
- HmacSHA1
- HmacSHA256
可以加密不同长度的数据。块密码算法用于处理大数据块。有几种分组密码操作模式,如下所列。我们不会在这里详细说明这些模式的工作原理,但可以在https://en.wikipedia/wiki/Block_cipher_mode_of_operation找到更多信息:
- ECB
- CBC
- CFB
- OFB
- PCBC
在我们加密或解密数据之前,我们需要一个密钥。
生成密钥
生成密钥的常用方法是使用KeyGenerator
类。该类没有公共构造函数,但重载getInstance
方法将返回一个KeyGenerator
实例。以下示例将 AES 算法与默认提供程序一起使用。此方法的其他版本允许选择提供者:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
该generateKey
方法返回一个对象的实例,该对象实现了SecretKey
接下来显示的接口。这是用于支持对称加密和解密的密钥:
SecretKey secretKey = keyGenerator.generateKey();
有了密钥,我们现在可以加密数据。
使用对称密钥加密文本
我们将encrypt
在后面的部分中 使用以下方法。此方法传递要加密的文本和密钥。术语纯文本经常用于指代未加密的数据。
本Cipher
类提供了加密过程的框架。该getInstance
方法返回使用 AES 算法的类的实例。该Cipher
实例被初始化为加密使用Cipher.ENCRYPT_MODE
作为第一个参数,密钥作为第二个参数。该doFinal
方法加密纯文本字节数组并返回加密的字节数组。所述Base64
类的getEncoder
返回编码加密的字节的编码器:
public static String encrypt( String plainText, SecretKey secretKey) { try { Cipher cipher = Cipher.getInstance("AES"); byte[] plainTextBytes = plainText.getBytes(); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedBytes = cipher.doFinal(plainTextBytes); Base64.Encoder encoder = Base64.getEncoder(); String encryptedText = encoder.encodeToString(encryptedBytes); return encryptedText; } catch (NoSuchAlgorithmException|NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) { // Handle exceptions } return null; }
编码加密的字节数组用于将其转换为字符串,以便我们稍后使用。编码字符串可能是一种有用的安全技术,如http://javarevisited.blogspot.sg/2012/03/why-character-array-is-better-than.html 中所述。
解密文本
解密文本的过程在下面显示的解密方法中说明。它使用反向过程,其中对加密的字节进行解码,并初始化Cipher
类的init
方法以使用密钥解密字节:
public static String decrypt(String encryptedText, SecretKey secretKey) { try { Cipher cipher = Cipher.getInstance("AES"); Base64.Decoder decoder = Base64.getDecoder(); byte[] encryptedBytes = decoder.decode(encryptedText); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); String decryptedText = new String(decryptedBytes); return decryptedText; } catch (NoSuchAlgorithmException|NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) { // Handle exceptions } return null; }
我们将在对称加密客户端/服务器部分中说明的回显客户端/服务器应用程序中使用这些方法。
非对称加密技术
非对称加密使用公钥和私钥。私钥由一个实体持有。公钥可供所有人使用。可以使用任一密钥加密数据:
- 如果数据使用私钥加密,则可以使用公钥解密
- 如果数据使用公钥加密,那么可以使用私钥解密
如果私钥的所有者发出用私钥加密的消息,则该消息的接收者可以用公钥对其进行解密。他们都可以阅读该消息,但他们知道只有私钥所有者才能发送此消息。
如果其他人使用公钥加密消息,则只有私钥所有者才能阅读该消息。但是,所有者无法确定是谁实际发送了消息。它可能是一个冒名顶替者。
但是,如果双方都有自己的一套公钥/私钥,我们可以保证只有发送方和接收方可以看到其内容。我们还可以保证发件人就是他们所说的那样。
假设Sue 想向 Bob 发送消息。Sue将使用她的私钥加密消息 M。我们将此消息称为 M1。然后她将使用 Bob 的公钥加密 M1 给我们 M2。然后将消息 M2 发送给 Bob。现在,只有 Bob 可以使用他的私钥解密此消息。这将返回 M1。Bob 现在可以使用 Sue 的公钥解密 M1 以获取原始消息 M。他知道这是来自 Sue,因为只有 Sue 的公钥会起作用。
这个发送消息的过程要求两个参与者都拥有自己的公钥/私钥。除此之外,它不如使用对称密钥有效。另一种方法是使用非对称密钥将秘密密钥传输给参与者。然后可以将密钥用于实际的消息传输。这是与 SSL 一起使用的技术。
有几种非对称算法。Java 支持以下加密算法:
- RSA
- Diffie-Hellman
- DSA
我们将使用AsymmetricKeyUtility
接下来声明的实用程序类来演示非对称加密/解密。此类封装了创建、保存、加载和检索公钥和私钥的方法。我们将在此处解释这些方法的工作原理,并稍后在非对称回显客户端/服务器应用程序中使用它们:
public class AsymmetricKeyUtility { public static void savePrivateKey(PrivateKey privateKey) { ... } public static PrivateKey getPrivateKey() { ... } public static void savePublicKey(PublicKey publicKey) { ... } public static PublicKey getPublicKey() { ... } public static byte[] encrypt(PublicKey publicKey, String message) { ... } public static String decrypt(PrivateKey privateKey, byte[] encodedData) { ... } public static void main(String[] args) { ... } }
生成和保存非对称密钥
该main
方法将创建密钥、保存它们,然后测试它们以查看它们是否正常工作。该KeyPairGenerator
方法将生成密钥。要使用非对称加密,我们使用 RSA 算法获取类的实例。该initialize
方法指定密钥使用 1,024 位。该generateKeyPair
方法生成的密钥,并且getPrivate
和getPublic
方法分别返回私钥和公钥,:
public static void main(String[] args) { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); ... } catch (NoSuchAlgorithmException ex) { // Handle exceptions }
我们将使用一组方法将这些密钥保存和检索到单独的文件中。这种方法不是最安全的,但它会简化回声客户端/服务器的使用。下面的语句调用 save 方法:
savePrivateKey(privateKey); savePublicKey(publicKey);
用于检索密钥的方法在此处调用:
privateKey = getPrivateKey();
publicKey = getPublicKey();
下一个代码序列测试加密/解密过程。encrypt
使用公钥创建消息并将其传递给方法。decrypt
调用该方法来解密消息。该encodedData
变量引用的加密数据:
String message = "The message";
System.out.println("Message: " + message);
byte[] encodedData = encrypt(publicKey,message);
System.out.println("Decrypted Message: " +
decrypt(privateKey,encodedData));
这个例子的输出如下:
消息:消息
解密消息:消息
相反,我们可以使用私钥进行加密,使用公钥进行解密,以达到相同的结果。
使用非对称密钥加密/解密文本
现在,让我们来看看具体的encrypt
和decrypt
方法。该encrypt
方法用于getInstance
获取 RSA 算法的实例。该init
方法指定Cipher
对象将使用公钥加密消息。该doFinal
方法执行实际的加密并返回一个包含加密消息的字节数组:
public static byte[] encrypt(PublicKey publicKey, String message) { byte[] encodedData = null; try { Cipher cipher = Cipher.getInstance("RSA "); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptedBytes = cipher.doFinal(message.getBytes()); encodedData = Base64.getEncoder().withoutPadding() .encode(encryptedBytes); } catch (NoSuchAlgorithmException|NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) { // Handle exceptions } return encodedData; }
decrypt
接下来Cipher
描述该方法。它指定实例将使用私钥解密消息。传递给它的加密消息必须先解码,然后该doFinal
方法才能对其进行解密。然后返回解密后的字符串:
public static String decrypt(PrivateKey privateKey, byte[] encodedData) { String message = null; try { Cipher cipher = Cipher.getInstance("RSA "); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decodedData = Base64.getDecoder().decode(encodedData); byte[] decryptedBytes = cipher.doFinal(decodedData); message = new String(decryptedBytes); } catch (NoSuchAlgorithmException|NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) { // Handle exceptions } return message; }
这两种方法都可以捕获加密/解密过程中可能发生的许多异常。我们不会在这里讨论这些例外情况。
将非对称密钥保存到文件
接下来的两种方法说明了一种保存和检索私钥的技术。本PKCS8EncodedKeySpec
类支持私有密钥的编码。编码后的密钥保存到private.key
文件中:
public static void savePrivateKey(PrivateKey privateKey) { try { PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); FileOutputStream fos = new FileOutputStream("private.key"); fos.write(pkcs8EncodedKeySpec.getEncoded()); fos.close(); } catch (FileNotFoundException ex) { // Handle exceptions } catch (IOException ex) { // Handle exceptions } }
getPrivateKey
接下来描述的方法从文件中返回一个私钥。本KeyFactory
类的generatePrivate
方法创建一个基于关键PKCS8EncodedKeySpec
指标:
public static PrivateKey getPrivateKey() { try { File privateKeyFile = new File("private.key"); FileInputStream fis = new FileInputStream("private.key"); byte[] encodedPrivateKey = new byte[(int) privateKeyFile.length()]; fis.read(encodedPrivateKey); fis.close(); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); return privateKey; } catch (FileNotFoundException ex) { // Handle exceptions } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) { // Handle exceptions } return null; }
公钥的 save 和 get 方法在下面描述。它们在使用的文件和类的使用上有所不同。这个类代表公钥:X509EncodedKeySpec
public static void savePublicKey(PublicKey publicKey) { try { X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded()); FileOutputStream fos = new FileOutputStream("public.key"); fos.write(x509EncodedKeySpec.getEncoded()); fos.close(); } catch (FileNotFoundException ex) { // Handle exceptions } catch (IOException ex) { // Handle exceptions } } public static PublicKey getPublicKey() { try { File publicKeyFile = new File("public.key"); FileInputStream fis = new FileInputStream("public.key"); byte[] encodedPublicKey = new byte[(int) publicKeyFile.length()]; fis.read(encodedPublicKey); fis.close(); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); return publicKey; } catch (FileNotFoundException ex) { // Handle exceptions } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) { // Handle exceptions } return null; }
标准加密算法名称可在https://docs.oracle/javase/8/docs/technotes/guides/security/StandardNames.html中找到。http://www.javamex/tutorials/cryptography/ciphers.shtml上提供了对称算法的性能比较。
创建密钥库
密钥库存储加密密钥和证书,并且经常与服务器和客户端结合使用。密钥库通常是一个文件,但也可以是硬件设备。Java 支持以下类型的密钥库条目:
- PrivateKey:这用于非对称加密
- 证书:这包含一个公钥
- SecretKey:这用于对称加密
Java 8 支持五种不同类型的密钥库:JKS、JCEKS、PKCS12、PKCS11 和 DKS:
- JKS:这是Java KeyStore ( JKS ),它的扩展名为. jks
- JCEKS:这是Java 加密扩展密钥库( JCE )。它可以存储所有三种密钥库实体类型,为密钥提供更强的保护,并使用扩展。 jceks
- PKCS12:与 JKS 和 JCEKS 相比,此密钥库可用于其他语言。它可以存储所有三种密钥库实体类型,并使用p12或的扩展名pfx。
- PKCS11:这是一种硬件密钥库类型。
- DKS:这是域密钥库( DKS ),其中包含其他密钥库的集合。
Java 中的默认密钥库类型是 JKS。可以使用keytool命令行工具或 Java 代码创建和维护密钥库。我们先演示一下keytool。
使用 keytool 创建和维护密钥库
keytool 是一个命令行程序,用于创建密钥库。其使用的完整文档可在https://docs.oracle/javase/8/docs/technotes/tools/unix/keytool.html 中找到。有几个 GUI 工具用于维护比 keytool 更易于使用的密钥库。其中之一是位于http://www-01.ibm/software/webservers/httpservers/doc/v1312/ibm/9atikeyu.htm 的IKEYMAN。
要在命令提示符下在 Windows 中使用 keytool,您需要配置 PATH 环境变量以定位其包含目录。使用类似于以下的命令:
C:\Some Directory>set path=C:\Program Files\Java\jdk1.8.0_25\bin;%path%
让我们使用 keytool 创建一个密钥库。在命令提示符下,输入以下命令。这将开始在名为 .key 的文件中创建密钥库的过程keystore.jks。别名是可用于引用密钥库的另一个名称:
C:\Some Directory>keytool -genkey -alias mykeystore -keystore keystore.jks
然后将提示您输入以下几条信息。根据需要响应提示。您输入的密码将不会显示。对于本章中的示例,我们使用了密码password:
输入密钥库密码:
重新输入新密码:
您的名字和姓氏是什么?
[Unknown]: some name
你的组织单位
叫什么名字? [未知]:开发
贵组织的名称是什么?
[未知]:mycom
您所在城市或地区的名称是什么?
[未知]:某个城市
您所在的州或省的名称是什么?
[Unknown]: some state
这个单位的两个字母的国家代码是什么?
[未知]:jv
然后将提示您确认输入,如下所示。yes如果值正确,请回复:
CN=some name, OU=development, O=mycom, L=some city, ST=some state, C=jv 是否正确?
[否]:是
您可以为密钥分配一个单独的密码,如下所示:
输入 <mykeystore> 的
密钥密码(如果与密钥库密码相同,则返回):
然后创建密钥库。可以使用–list参数显示密钥库的内容,如下所示。该–v选项会产生详细的输出:
keytool -list -v -keystore keystore.jks -alias mykeystore
这将显示以下输出。需要输入密钥库密码和别名:
输入密钥库密码:
别名:mykeystore
创建日期:2015 年 10 月 22 日
条目类型:PrivateKeyEntry
证书链长度:1
证书[1]:
所有者:CN=some name, OU=development, O=mycom, L=some city , ST=some state, C=jv
发行人:CN=some name, OU=development, O=mycom, L=some city, ST=some state, C=jv
序列号:39f2e11e
有效期:10 月 22 日星期四 18 :11:21 CDT 2015 直到: Wed Jan 20 17:11:21 CST 2016
证书指纹:
MD5: 64:44:64:27:85:99:01:22:49:FC:41:DA:F7:A8 :4C:35
SHA1: 48:57:3A:DB:1B:16:92:E6:CC:90:8B:D3:A7:A3:89:B3:9C:9B:7C:BB
SHA256: B6:B2 :22:A0:64:61:DB:53:33:04:78:77:38:AF:D2:A0:60:37:A6:CB:3F:
3C:47:CC:30:5F:02:86:8F:68:84:7D
签名算法名称:SHA1withDSA
版本:3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 07 D9 51 BE A7 48 23 34 5F 8E C6 F9 88 C0 36 CA ..Q..H#4_...6.
0010: 27 8E 04 22 '.."
]
]
Keytool 命令行参数
输入密钥库的信息可能很乏味。简化此过程的一种方法是使用命令行参数。以下命令将创建以前的密钥库:
keytool -genkeypair -alias mykeystore -keystore keystore.jks -keypass password -storepass password -dname "cn=some name, ou=development, o=mycom, l=some city, st=some state c=jv
您会注意到命令行末尾没有匹配的双引号。不需要。命令行参数记录在前面列出的 keytool 网站上。
此工具可以创建对称和非对称密钥以及证书。以下一系列命令演示了其中几种类型的操作。我们将为一对非对称密钥创建一个密钥库。然后将导出可用于服务器和客户端应用程序的一对证书。
此命令将serverkeystore.jck使用 RSA 算法创建密钥库文件,密钥大小为 1,024 位,有效期为 365 天:
keytool -genkeypair -alias server -keyalg RSA -keysize 1024 -storetype jceks -validity 365 -keypass password -keystore serverkeystore.jck -storepass password -dname "cn=localhost, ou=Department, o=MyComp Inc, l=Some City, st=合资企业 c=美国
此命令生成clientkeystore.jck供客户端应用程序使用的密钥库:
keytool -genkeypair -alias client -keyalg RSA -keysize 1024 -storetype jceks -validity 365 -keypass password -keystore clientkeystore.jck -storepass password -dname "cn=localhost, ou=Department, o=MyComp Inc, l=Some City, st=合资企业 c=美国
接下来为客户端创建一个证书文件并将其放置在client.crt文件中:
keytool -export -alias client -storetype jceks -keystore clientkeystore.jck -storepass password -file client.crt
服务器的证书在此处导出:
keytool -export -alias server -storetype jceks -keystore serverkeystore.jck -storepass password -file server.crt
信任库是用于验证凭据的文件,而密钥库将生成凭据。凭证通常采用证书的形式。信任存储通常持有来自受信任第三方的证书以形成证书链。
以下命令创建the clienttruststore.jck文件,这是客户端的信任库:
keytool -importcert -alias server -file server.crt -keystore clienttruststore.jck -keypass password -storepass storepassword
此命令生成以下输出:
所有者:CN=localhost, OU=Department, O=MyComp Inc, L=Some City, ST="JV c=US"
发行人:CN=localhost, OU=Department, O=MyComp Inc, L=Some City, ST= “JV c=US”
序列号:2d924315
有效期:2015 年 10 月 20 日星期二 19:26:00 CDT 至:2016 年 10 月 19 日星期三 19:26:00 CDT
证书指纹:
MD5:9E:3D:0E:D7:02: 7A:F5:23:95:1E:24:B0:55:A9:F7:95
SHA1: 69:87:CE:EE:11:59:8F:40:A8:14:DA:D3:92:D0 :3F:B6:A9:5A:7B:53
SHA256: BF:C1:7B:6D:D0:39:67:2D:1C:68:27:79:31:AA:B8:70:2B:FD: 1C:85:18:
EC:5B:D7:4A:48:03:FA:F1:B8:CD:4E
签名算法名称:SHA256withRSA
版本:3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: D3 63 C9 60 6D 04 49 75 FB E8 F7 90 30 1D C6 C1 .c.`m.Iu....0...
0010: 10 DF 00 CF ....
]
]
信任这个证书?[no]: yes
证书已添加到密钥库
服务器的信任库是使用以下命令创建的:
keytool -importcert -alias client -file client.crt -keystore servertruststore.jck -keypass password -storepass password
其输出如下:
所有者:CN=localhost, OU=Department, O=MyComp Inc, L=Some City, ST="JV c=US"
发行人:CN=localhost, OU=Department, O=MyComp Inc, L=Some City, ST= “JV c=US”
序列号:5d5f3c40
有效期自:2015 年 10 月 20 日星期二 19:27:31 CDT 至:2016 年 10 月 19 日星期三 19:27:31 CDT
证书指纹:
MD5:0E:FE:B3:EB:1B: D2:AD:32:9C:BC:FB:43:40:85:C1:A7
SHA1: 90:14:1E:17:DF:51:79:0B:1E:A3:EC:38:6B:BA :A6:F4:6F:BF:B6:D2
SHA256: 7B:3E:D8:2C:04:ED:E5:52:AE:B4:00:A8:63:A1:13:A7:E1:8E: 59:63:E8:
86:38:D8:09:55:EA:3A:7C:F7:EC:4B
签名算法名称:SHA256withRSA
版本:3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: D9 53 34 3B C0 11 F8 75 0F 18 4E 18 23 A2 47 FE .S4;...u..N.#.G.
0010:E6 F5 C1 AF ....
]
]
信任这个证书?[no]: yes
证书已添加到密钥库
我们现在将演示如何在 Java 中执行类似的操作。
使用 Java 创建和维护密钥库
可以直接使用 Java代码创建密钥库、它们的密钥和证书。在本节中,我们将演示如何创建包含密钥的密钥库。我们将在对称加密客户端/服务器部分使用这个类。
在SymmetricKeyStoreCreation类被声明如下。该SymmetricKeyStoreCreation方法创建一个密钥库,而该main方法生成并存储密钥:
public class SymmetricKeyStoreCreation { private static KeyStore createKeyStore(String fileName, String pw) { ... } public static void main(String[] args) { ... } }
createKeyStore接下来描述该方法。它传递了密钥库的文件名和密码。KeyStore创建了一个实例,它指定了一个 JCEKS 密钥库。如果密钥库已经存在,它将返回该密钥库:
private static KeyStore createKeyStore(String fileName, String password) { try { File file = new File(fileName); final KeyStore keyStore = KeyStore.getInstance("JCEKS"); if (file.exists()) { keyStore.load(new FileInputStream(file), password.toCharArray()); } else { keyStore.load(null, null); keyStore.store(new FileOutputStream(fileName), password.toCharArray()); } return keyStore; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) { // Handle exceptions } return null; }
在该main方法中,KeyGenerator使用 AES 算法创建一个实例。该generateKey方法将创建SecretKey实例,如下所示:
public static void main(String[] args) { try { final String keyStoreFile = "secretkeystore.jks"; KeyStore keyStore = createKeyStore(keyStoreFile, "keystorepassword"); KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecretKey secretKey = keyGenerator.generateKey(); ... } catch (Exception ex) { // Handle exceptions } }
本KeyStore.SecretKeyEntry类表示密钥库的条目。我们需要这个和一个代表密码的KeyStore.PasswordProtection类的实例来存储密钥:
KeyStore.SecretKeyEntry keyStoreEntry = new KeyStore.SecretKeyEntry(secretKey); KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection( "keypassword".toCharArray());
该setEntry方法使用字符串别名、密钥库条目对象和密码来存储条目,如下所示:
keyStore.setEntry("secretKey", keyStoreEntry, keyPassword);
然后将此条目写入密钥库:
keyStore.store(new FileOutputStream(keyStoreFile), "keystorepassword".toCharArray());
使用 Java 可以进行其他密钥库操作。
对称加密客户端/服务器
本节演示如何在客户端/服务器应用程序中使用对称加密/解密。下面的示例实现了一个简单的 echo 客户端/服务器,使我们可以专注于基本过程,而不会偏离特定的客户端/服务器问题。服务器使用SymmetricEchoServer
类实现,客户端使用SymmetricEchoClient
类实现。
客户端将加密消息并将其发送到服务器。然后服务器将解密该消息并以纯文本形式将其发回。如果需要,可以轻松地对响应进行加密。这种单向加密足以说明基本过程。
在 Windows 中运行本章中讨论的应用程序时,您可能会遇到以下对话框。选择允许访问按钮以允许应用程序运行:
我们还将使用SymmetricKeyStoreCreation
在对称加密技术中开发的类。
对称服务器应用
接下来声明对称服务器。它拥有一个main
,decrypt
和getSecretKey
方法。该decrypt
方法从客户端获取加密消息并对其进行解密。该getSecretKey
方法将从以对称加密技术创建的密钥库中提取密钥。该main
方法包含用于与客户端通信的基本套接字和流:
public class SymmetricEchoServer { private static Cipher cipher; public static String decrypt(String encryptedText, SecretKey secretKey) { ... } private static SecretKey getSecretKey() { ... } public static void main(String[] args) { ... } }
该decrypt
方法与对称加密技术中开发的方法相同,此处不再赘述。getSecretKey
接下来描述该方法。secretkeystore.jks
以对称加密技术创建的文件保存着密钥。此方法使用许多main
在SymmetricKeyStoreCreation
该类的方法中使用的相同类。KeyStore.PasswordProtection
该类的一个实例用于从密钥库中提取密钥。密钥库密码keystorepassword
被硬编码到应用程序中。这不是最佳实践,但它简化了示例:
private static SecretKey getSecretKey() { SecretKey keyFound = null; try { File file = new File("secretkeystore.jks"); final KeyStore keyStore = KeyStore.getInstance("JCEKS"); keyStore.load(new FileInputStream(file), "keystorepassword".toCharArray()); KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection( "keypassword".toCharArray()); KeyStore.Entry entry = keyStore.getEntry("secretKey", keyPassword); keyFound = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) { // Handle exceptions } catch (UnrecoverableEntryException ex) { // Handle exceptions; } return keyFound; }
该main
方法是非常相似,在开发服务器第1章,入门网络编程。主要区别在于 while 循环内。来自客户端的输入decrypt
与密钥一起传递给方法,如下所示。然后显示解密的文本并返回给客户端:
String decryptedText = decrypt(inputLine, getSecretKey());
该main
方法如下:
public static void main(String[] args) { System.out.println("Simple Echo Server"); try (ServerSocket serverSocket = new ServerSocket(6000)) { System.out.println("Waiting for connection....."); Socket clientSocket = serverSocket.accept(); System.out.println("Connected to client"); try (BufferedReader br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); PrintWriter out = new PrintWriter( clientSocket.getOutputStream(), true)) { String inputLine; while ((inputLine = br.readLine()) != null) { String decryptedText = decrypt(inputLine, getSecretKey()); System.out.println("Client request: " + decryptedText); out.println(decryptedText; } } catch (IOException ex) { // Handle exceptions } catch (Exception ex) { // Handle exceptions } } catch (IOException ex) { // Handle exceptions } System.out.println("Simple Echo Server Terminating"); }
现在,让我们检查客户端应用程序。
对称客户端应用程序
客户端应用程序描述未来,是非常相似的是在开发客户端应用程序第1章,入门网络编程。它使用getSecretKey
在服务器中使用的相同方法。encrypt
对称加密技术中解释的方法用于加密用户的消息。这两种方法在这里都没有重复:
public class SymmetricEchoClient { private static Cipher cipher; public static String encrypt(String plainText, SecretKey secretKey) { ... } ... } public static void main(String args[]) { ... } }
在main
从while循环的版本不同方法第1章,入门网络编程。以下语句加密用户消息:
String encryptedText = encrypt(inputLine, getSecretKey());
该main
方法如下:
public static void main(String args[]) { System.out.println("Simple Echo Client"); try (Socket clientSocket = new Socket(InetAddress.getLocalHost(), 6000); PrintWriter out = new PrintWriter( clientSocket.getOutputStream(), true); BufferedReader br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream()))) { System.out.println("Connected to server"); Scanner scanner = new Scanner(System.in); while (true) { System.out.print("Enter text: "); String inputLine = scanner.nextLine(); if ("quit".equalsIgnoreCase(inputLine)) { break; } String encryptedText = encrypt(inputLine, getSecretKey()); System.out.println( "Encrypted Text After Encryption: " + encryptedText); out.println(encryptedText); String response = br.readLine(); System.out.println( "Server response: " + response); } } catch (IOException ex) { // Handle exceptions } catch (Exception ex) { // Handle exceptions } }
我们现在准备好看看客户端和服务器是如何交互的。
运行中的对称客户端/服务器
应用程序的行为方式与它们在第 1 章,网络编程入门中的行为方式相同。唯一的区别是发送到服务器的消息是加密的。除了在客户端显示加密文本外,此加密在应用程序的输出中不可见。一种可能的交互如下。服务器输出首先显示:
Simple Echo Server
Waiting for connection.....
Connected to client
Client request: The first message
Client request: The second message
Simple Echo Server Terminating
以下是客户端的应用程序输出:
Connected to server
Enter text: The first message
Encrypted Text After Encryption: drkvP3bhnfMXrZluFiqKb0RgjoDqFIJMCo97YqqgNuM=
Server response: drkvP3bhnfMXrZluFiqKb0RgjoDqFIJMCo97YqqgNuM=
Enter text: The second message
Encrypted Text After Encryption: fp9g+AqsVqZpxKMVNx8IkNdDcr9IGHb/qv0qrFinmYs=
Server response: fp9g+AqsVqZpxKMVNx8IkNdDcr9IGHb/qv0qrFinmYs=
Enter text: quit
我们现在将使用非对称密钥复制此功能。
非对称加密客户端/服务器
AsymmetricKeyUtility
在非对称加密技术中开发的类用于支持客户端和服务器应用程序。我们将使用它encrypt
和decrypt
方法。客户端和服务器应用程序的结构与前几节中使用的结构相似。客户端将向服务器发送一条加密消息,服务器将对其进行解密,然后以纯文本进行响应。
非对称服务器应用
AsymmetricEchoServer
如下声明的类用于服务器。该main
方法是它的唯一方法。创建了一个服务器套接字,它阻塞在accept
等待客户端请求的方法上:
public class AsymmetricEchoServer { public static void main(String[] args) { System.out.println("Simple Echo Server"); try (ServerSocket serverSocket = new ServerSocket(6000)) { System.out.println("Waiting for connection....."); Socket clientSocket = serverSocket.accept(); System.out.println("Connected to client"); ... } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException ex) { // Handle exceptions } System.out.println("Simple Echo Server Terminating"); } }
在接受客户端连接 IO 后,将建立流并inputLine
实例化一个大小为的字节数组171
。这是正在发送的消息的大小,使用此值将避免各种异常:
try (DataInputStream in = new DataInputStream( clientSocket.getInputStream()); PrintWriter out = new PrintWriter( clientSocket.getOutputStream(), true);) { byte[] inputLine = new byte[171]; ... } } catch (IOException ex) { // Handle exceptions } catch (Exception ex) { // Handle exceptions }
为了执行解密,我们需要一个私钥。这是使用以下getPrivateKey
方法获得的:
PrivateKey privateKey = AsymmetricKeyUtility.getPrivateKey();
while 循环将从客户端读取加密消息。decrypt
使用消息和私钥调用该方法。然后显示解密的消息并将其发送回客户端。如果消息是quit
,则服务器终止:
while (true) { int length = in.read(inputLine); String buffer = AsymmetricKeyUtility.decrypt( privateKey, inputLine); System.out.println( "Client request: " + buffer); if ("quit".equalsIgnoreCase(buffer)) { break; } out.println(buffer);
现在,让我们检查客户端应用程序。
非对称客户端应用程序
客户端应用程序位于AsymmetricEchoClient
类中,如下所示。它也只有一种main
方法。建立服务器连接后,将建立 IO 流:
public class AsymmetricEchoClient { public static void main(String args[]) { System.out.println("Simple Echo Client"); try (Socket clientSocket = new Socket(InetAddress.getLocalHost(), 6000); DataOutputStream out = new DataOutputStream( clientSocket.getOutputStream()); BufferedReader br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); DataInputStream in = new DataInputStream( clientSocket.getInputStream())) { System.out.println("Connected to server"); ... } } catch (IOException ex) { // Handle exceptions } catch (Exception ex) { // Handle exceptions } } }
该Scanner
班是用来获取用户输入。公钥用于加密用户消息,并使用AsymmetricKeyUtility
类的getPublicKey
方法获得:
Scanner scanner = new Scanner(System.in); PublicKey publicKey = AsymmetricKeyUtility.getPublicKey();
在接下来的 while 循环中,会提示用户输入一条消息,该消息使用该encrypt
方法进行加密。然后将加密的消息发送到服务器。如果消息是quit
,则程序终止:
while (true) { System.out.print("Enter text: "); String inputLine = scanner.nextLine(); byte[] encodedData = AsymmetricKeyUtility.encrypt( publicKey, inputLine); System.out.println(encodedData); out.write(encodedData); if ("quit".equalsIgnoreCase(inputLine)) { break; } String message = br.readLine(); System.out.println("Server response: " + message);
现在,我们可以一起使用这些应用程序。
非对称客户端/服务器在行动
启动服务器,然后启动客户端。客户端将提示输入一系列消息。下面显示了一种可能的交换的输出。首先显示服务器端:
Simple Echo Server
Waiting for connection.....
Connected to client
Client request: The first message
Client request: The second message
Client request: quit
Simple Echo Server Terminating
下面显示了客户端交互:
Simple Echo Client
Connected to server
Enter text: The first message
[B@6bc168e5
Server response: The first message
Enter text: The second message
[B@7b3300e5
Server response: The second message
Enter text: quit
[B@2e5c649
TLS/SSL
TLS/SSL是一组用于保护 Internet 上许多服务器的协议。SSL 是 TLS 的继承者。然而,它们并不总是可以互换的。SSL 使用消息验证码( MAC )算法,而 TLS 使用消息验证码哈希( HMAC )算法。
SSL 通常与许多其他协议一起使用,包括文件传输协议( FTP )、Telnet、网络新闻传输协议( NNTP )、轻型目录访问协议( LDAP ) 和交互式消息访问协议( IMAP )。
TLS/SSL 在提供这些功能时确实会导致性能下降。然而,随着互联网速度的提高,这种打击通常并不显着。
当使用 HTTPS 协议时,用户会知道,因为该协议通常存在于浏览器的地址字段中。它甚至用于您可能意想不到的地方,例如以下 Google URL:
我们不会深入研究 SSL 协议如何工作的细节。但是,可以在http://www.javacodegeeks/2013/04/understanding-transport-layer-security-secure-socket-layer.html找到对该协议的简要讨论。在本节中,我们将说明如何创建和使用 SSL 服务器以及用于支持此协议的 Java 类。
为了简化应用程序,客户端向服务器发送一条消息,然后服务器将其显示出来。没有响应被发送回客户端。客户端使用 SSL 连接到服务器并与之通信。使用 SSL 将消息返回给客户端作为练习留给读者。
SSL服务器
服务器在以下SSLServer
类中实现。所有代码都可以在main
方法中找到。我们将使用keystore.jks
密钥库访问以对称加密技术创建的密钥。为了提供对密钥库的访问,使用一个Provider
实例来指定密钥库及其密码。在代码中硬编码密码不是一个好主意,但它用于简化此示例:
public class SSLServer { public static void main(String[] args) throws Exception { System.out.println("SSL Server Started"); Security.addProvider(new Provider()); System.setProperty("javax.ssl.keyStore", "keystore.jks"); System.setProperty("javax.ssl.keyStorePassword", "password"); ... } }
SSLServerSocket
该类的一个实例用于在客户端和服务器之间建立通信。这个实例是使用SSLServerSocketFactory
类的getDefault
方法创建的。与之前的服务器套接字类似,该accept
方法会阻塞,直到建立客户端连接:
SSLServerSocketFactory sslServerSocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketfactory.createServerSocket(5000); System.out.println("Waiting for a connection"); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); System.out.println("Connection established");
BufferedReader
然后从套接字的输出流创建一个实例:
PrintWriter pw = new PrintWriter(sslSocket.getOutputStream(), true); BufferedReader br = new BufferedReader( new InputStreamReader(sslSocket.getInputStream()));
下面的 while 循环读取客户端请求并显示它。如果消息是quit
,则服务器终止:
String inputLine; while ((inputLine = br.readLine()) != null) { pw.println(inputLine); if ("quit".equalsIgnoreCase(inputLine)) { break; } System.out.println("Receiving: " + inputLine); }
SSL 套接字自动处理加密和解密。
笔记
在 Mac 上,服务器在执行时可能会抛出异常。这可以通过创建 PKCS12 密钥库并使用该-Djavax.ssl.keyStoreType=pkcs12 VM
选项来避免。
SSL客户端
的SSLClient
类实现客户端应用程序,下一个,如图所示。它使用与服务器基本相同的过程。while 循环以与以前的客户端应用程序中执行的方式相同的方式处理用户输入:
public class SSLClient { public static void main(String[] args) throws Exception { System.out.println("SSL Client Started"); Security.addProvider(new Provider()); System.setProperty("javax.ssl.trustStore", "keystore.jks"); System.setProperty("javax.ssl.trustStorePassword", "password"); SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslSocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 5000); System.out.println( "Connection to SSL Server Established"); PrintWriter pw = new PrintWriter(sslSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(sslSocket.getInputStream())); Scanner scanner = new Scanner(System.in); while (true) { System.out.print("Enter a message: "); String message = scanner.nextLine(); pw.println(message); System.out.println("Sending: " + in.readLine()); if ("quit".equalsIgnoreCase(message)) { break; } } pw.close(); in.close(); sslSocket.close(); } }
让我们看看他们是如何互动的。
SSL 客户端/服务器运行中
启动服务器,然后启动客户端。在以下输出中,三个消息被发送到服务器,然后显示:
SSL Server Started
Waiting for a connection
Connection established
Receiving: The first message
Receiving: The second message
客户端输入如下所示:
SSL Client Started
Connection to SSL Server Established
Enter a message: The first message
Sending: The first message
Enter a message: The second message
Sending: The second message
Enter a message: quit
Sending: quit
本SSLServerSocket
类提供执行支持SSL的服务器的简单方法。
安全哈希函数
当给定某种文档时,安全散列函数将生成一个很大的数字,称为散列值。该文档几乎可以是任何类型。我们将在示例中使用简单的字符串。
该函数是一种单向哈希函数,这意味着在给定哈希值时实际上不可能重新创建文档。当与非对称密钥结合使用时,它允许在保证文档未被更改的情况下传输文档。
文档的发送者将使用安全散列函数来生成文档的散列值。发件人将用他们的私钥加密这个哈希值。然后将文档和密钥组合并发送到接收器。文档未加密。
收到文件后,接收方将使用发送方的公钥解密哈希值。然后,接收方将对文档使用相同的安全散列函数来获得散列值。如果该哈希值与解密的哈希值匹配,则保证接收方文档未被修改。
目的不是加密文档。虽然可能,但当向第三方隐藏文档并不重要而仅提供文档未被修改的保证时,此方法很有用。
Java 支持以下散列算法:
- MD5 : 默认大小为 64 字节
- SHA1:默认大小为 64 字节
我们将在示例中使用 SHA 哈希函数。这一系列功能是由美国国家安全局(NSA)开发的。此哈希函数有三个版本:SHA-0、SHA-1 和 SHA-2。SHA-2 是更安全的算法,并使用可变摘要大小:SHA-224、SHA-256、SHA-384 和 SHA-512。
的MessageDigest类可与任意大小的数据生成固定大小的哈希值。这个类没有公共构造函数。getInstance当给定算法名称时,该方法返回类的一个实例。有效名称可在http://docs.oracle/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest中找到。在这个例子中,我们使用SHA-256:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(message.getBytes());
完整的示例,改编自http://www.mkyong/java/java-sha-hashing-example/,如下所示。该displayHashValue方法提取单个哈希值字节并将它们转换为可打印格式:
public class SHAHashingExample { public static void main(String[] args) throws Exception { SHAHashingExample example = new SHAHashingExample(); String message = "This is a simple text message"; byte hashValue[] = example.getHashValue(message); example.displayHashValue(hashValue); } public void displayHashValue(byte hashValue[]) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < hashValue.length; i++) { builder.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); } System.out.println("Hash Value: " + builder.toString()); } public byte[] getHashValue(String message) { try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(message.getBytes()); return messageDigest.digest(); } catch (NoSuchAlgorithmException ex) { // Handle exceptions } return null; } }
执行程序。这将产生以下输出:
哈希值:83c660972991049c25e6cad7a5600fc4d7c062c097b9a75c1c4f13238375c26c
可以在http://howtodoinjava/2013/07/22/how-to-generate-secure-password-hash-md5-sha-pbkdf2-bcrypt 中找到对用 Java 实现的安全散列函数的更详细的检查-例子/。
概括
在本章中,我们介绍了几种 Java 方法来保护应用程序之间的通信。我们首先简要介绍了与安全相关的术语,然后在介绍之后进行了更详细的讨论。
目前使用两种常见的加密/解密方法。第一种是对称密钥加密,它使用在应用程序之间共享的单个密钥。这种方法要求以安全的方式在应用程序之间传输密钥。
第二种方法使用非对称加密。该技术使用私钥和公钥。用这些密钥之一加密的消息可以用另一个密钥解密。通常,公钥是使用来自可信来源的证书分发的。私钥的持有者需要保护它,以便其他人无法访问它。公钥可以与任何需要它的人自由共享。
加密密钥通常存储在允许以编程方式访问密钥的密钥库中。密钥库是使用 keytool 应用程序创建和维护的。我们演示了在我们的几个应用程序中创建和使用密钥库。此外,我们使用对称密钥和非对称密钥对来支持回显客户端/服务器应用程序。
创建安全客户端和服务器的更常见方法是使用SSLServerSocket
该类。这根据在密钥库中找到的秘密密钥执行数据的自动加密和解密。我们演示了如何在服务器和客户端应用程序中使用该类。
我们还研究了安全散列函数的使用。这种技术允许传输未加密的数据并保证它没有被修改。非对称密钥对用于加密散列值。我们提供了此过程的一个简单示例。
在下一章中,我们将研究影响分布式应用程序之间交互的各种因素。
本文标签: 网络安全
版权声明:本文标题:第 8 章网络安全 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1729561171a1206275.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论