# 弱点和替代品

> 原文： [https://docs.oracle.com/javase/tutorial/security/apisign/enhancements.html](https://docs.oracle.com/javase/tutorial/security/apisign/enhancements.html)

本课程中的`GenSig`和`VerSig`程序说明了如何使用 JDK Security API 为数据生成数字签名并验证签名是否可信。但是，这些程序描述的实际场景，其中发送方使用 JDK 安全 API 生成新的公钥/私钥对，发送方将编码的公钥字节存储在文件中，接收方读入密钥字节，不一定是现实的，并且有潜在的重大缺陷。

在许多情况下，不需要生成密钥;它们已经存在，无论是作为文件中的编码键还是作为密钥库中的条目。

潜在的主要缺陷是没有任何东西可以保证接收方收到的公钥的真实性，只有当提供的公钥是 _ 本身 _ 真实时，`VerSig`程序才能正确验证签名的真实性！

## 使用编码密钥字节

有时，编码的密钥字节已经存在于用于签名和验证的密钥对的文件中。如果是这种情况，`GenSig`程序可以导入编码的私钥字节并将它们转换为签名所需的`PrivateKey`，通过以下方式，假设包含私钥字节的文件的名称在`privkeyfile`中`String`并且字节表示已使用 PKCS＃8 标准编码的 DSA 密钥。

```
FileInputStream keyfis = new FileInputStream(privkeyfile);
byte[] encKey = new byte[keyfis.available()];
keyfis.read(encKey);
keyfis.close();

PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encKey);

KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);

```

`GenSig`不再需要将公钥字节保存在文件中，因为它们已经在一个文件中。

在这种情况下，发送者发送接收者

*   已存在的文件包含编码的公钥字节（除非接收器已经有这个）和
*   由`GenSig`导出的数据文件和签名文件。

`VerSig`程序保持不变，因为它已经预期文件中的编码公钥字节。

但是，恶意用户拦截文件并以无法检测到其交换机的方式替换所有文件的潜在问题又如何呢？在某些情况下，这不是问题，因为人们已经面对面地或通过进行面对面交流的可信第三方交换了公钥。之后，可以远程完成多个后续文件和签名交换（即，在不同位置的两个人之间），并且可以使用公钥来验证它们的真实性。如果恶意用户尝试更改数据或签名，则`VerSig`会检测到此情况。

如果无法进行面对面密钥交换，您可以尝试其他方法来增加正确接收的可能性。例如，您可以在后续数据和签名文件交换之前使用最安全的方法发送公钥，可能使用安全性较低的介质。

通常，将数据和签名与公钥分开发送可以大大降低发生攻击的可能性。除非所有三个文件都被更改，并且以下一段中讨论的某种方式，`VerSig`将检测到任何篡改。

如果恶意用户拦截了所有三个文件（数据文档，公钥和签名），该人可以用其他内容替换该文档，使用私钥对其进行签名，并将替换后的文档转发给您，新签名，以及与用于生成新签名的私钥对应的公钥。然后`VerSig`将报告成功的验证，并且您认为该文档来自原始发件人。因此，您应该采取措施确保至少完整地接收公钥（`VerSig`检测到对其他文件的任何篡改），或者您可以使用证书来促进公钥的身份验证，如下一节所述。

## 使用证书

在密码学中更常见的是交换包含公钥而不是密钥本身的 _ 证书 _。

一个好处是证书由一个实体（_ 发行者 _）签署，以验证所附公钥是否是另一个实体（_ 主题 _ 或 _ 所有者的实际公钥）_ ）。通常，受信任的第三方 _ 证书颁发机构 _（CA）会验证主题的身份，然后通过签署证书来担保其作为公钥的所有者。

使用证书的另一个好处是，您可以使用其颁发者（签名者）公钥来检查以确保您通过验证其数字签名而获得的证书的有效性，该公钥本身可以存储在证书中，该证书的签名可以通过使用该证书发行人的公钥;公钥本身可以存储在证书中，依此类推，直到您找到您已经信任的公钥。

如果您无法建立信任链（可能因为您无法获得所需的颁发者证书），则可以计算证书**指纹**。每个指纹都是一个相对较短的数字，可以唯一且可靠地识别证书。 （从技术上讲，它是证书信息的哈希值，使用消息摘要，也称为单向散列函数。）您可以调用证书所有者并将收到的证书的指纹与发送的证书进行比较。如果它们相同，则证书是相同的。

`GenSig`创建包含公钥的证书和`VerSig`然后导入证书并提取公钥会更安全。但是，JDK 没有允许您从公钥创建证书的公共证书 API，因此`GenSig`程序无法从其生成的公钥创建证书。 （_ 是用于从证书中提取公钥的 _ 公共 API。）

如果需要，您可以使用各种安全工具而不是 API 来签署重要文档并使用密钥库中的证书，如[交换文件](../toolfilex/index.html)课程中所做的那样。

或者，您可以使用 API​​修改程序，以使用密钥库中已有的私钥和相应的公钥（在证书中）。首先，修改`GenSig`程序以从密钥库中提取私钥，而不是生成新密钥。首先，我们假设以下内容：

*   密钥库名称位于`String` `ksName`中
*   密钥库类型是“JKS”，Oracle 的专有类型。
*   密钥库密码位于 char 数组`spass`中
*   包含私钥的密钥库条目的别名和公钥证书位于`String` `alias`中
*   私钥密码位于 char 数组`kpass`中

然后，您可以通过以下方法从密钥库中提取私钥。

```
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream ksfis = new FileInputStream(ksName); 
BufferedInputStream ksbufin = new BufferedInputStream(ksfis);

ks.load(ksbufin, spass);
PrivateKey priv = (PrivateKey) ks.getKey(alias, kpass);

```

您可以从密钥库中提取公钥证书，并通过以下方法将其编码的字节保存到名为`suecert`的文件中。

```
java.security.cert.Certificate cert = ks.getCertificate(alias);
byte[] encodedCert = cert.getEncoded();

// Save the certificate in a file named "suecert" 

FileOutputStream certfos = new FileOutputStream("suecert");
certfos.write(encodedCert);
certfos.close();

```

然后将数据文件，签名和证书发送给接收方。接收方通过`keytool -printcert`命令首先获取证书的指纹来验证证书的真实性。

```
keytool -printcert -file suecert
Owner: CN=Susan Jones, OU=Purchasing, O=ABC, L=Cupertino, ST=CA, C=US
Issuer: CN=Susan Jones, OU=Purchasing, O=ABC, L=Cupertino, ST=CA, C=US
Serial number: 35aaed17
Valid from: Mon Jul 13 22:31:03 PDT 1998 until:
Sun Oct 11 22:31:03 PDT 1998
Certificate fingerprints:
MD5:  1E:B8:04:59:86:7A:78:6B:40:AC:64:89:2C:0F:DD:13
SHA1: 1C:79:BD:26:A1:34:C0:0A:30:63:11:6A:F2:B9:67:DF:E5:8D:7B:5E

```

然后，接收方可以通过呼叫发送方并将它们与发送方证书的指纹进行比较或在公共存储库中查找指纹来验证指纹。

接收者的验证程序（修改后的`VerSig`）然后可以导入证书并通过以下方式从中提取公钥，假设证书文件名（例如，`suecert`）在`String` `certName`中。

```
FileInputStream certfis = new FileInputStream(certName);
java.security.cert.CertificateFactory cf =
    java.security.cert.CertificateFactory.getInstance("X.509");
java.security.cert.Certificate cert =  cf.generateCertificate(certfis);
PublicKey pub = cert.getPublicKey();

```

## 确保数据机密性

假设您希望保密数据内容，以便人们在传输过程中（或在您自己的计算机或磁盘上）意外或恶意地尝试查看数据内容时不能这样做。为了保密数据，您应对其进行加密并存储并仅发送加密结果（称为 _ 密文 _）。接收器可以解密密文以获得原始数据的副本。