弱点和替代方案

本课中的GenSigVerSig程序说明了如何使用 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检测到。

如果无法进行面对面的密钥交换,则可以try其他方法来增加正确接收密钥的可能性。例如,您可以在以后交换数据和签名文件之前,通过可能使用的最不安全的方法,通过最安全的方法发送公用密钥。

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

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

使用证书

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

一个好处是,证书由一个实体(发行者)签名,以验证所包含的公钥是另一实体(主题或所有者)的实际公钥。通常,受信任的第三方证书颁发机构(CA)验证主题的身份,然后通过对证书签名来保证其是公钥的所有者。

使用证书的另一个好处是,您可以使用其发行者的(签名者)公共密钥来验证其数字签名,从而检查所接收证书的有效性,该数字签名本身可以存储在可以通过使用以下命令验证其签名的证书中:该证书发行者的公钥;该公用密钥本身可以存储在证书中,依此类推,直到获得您已经信任的公用密钥。

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

GenSig创建包含公钥的证书,然后VerSig然后导入证书并提取公钥,将更加安全。但是,JDK 没有公共证书 API,该 API 允许您从公共密钥创建证书,因此GenSig程序无法从其生成的公共密钥创建证书。 (不过,有一些公共 API 可用于从证书中提取公共密钥.)

如果需要的话,您可以使用各种安全工具(而不是 API)来签名重要文档并使用密钥库中的证书,就像在Exchanging Files类中所做的那样。

或者,您可以使用 API 修改程序,使其与密钥库中已经存在的私钥和相应的公钥(在证书中)一起使用。首先,修改GenSig程序以从密钥库中提取私钥,而不是生成新密钥。首先,让我们假设以下内容:

  • 密钥库名称在String ksName

  • 密钥库类型为“ JKS”,这是 Oracle 的专有类型。

  • 密钥库密码在 char 数组spass

  • 包含私钥和公用密钥证书的密钥库条 Object 别名位于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

然后,接收者可以通过调用发送者并将其与发送者证书的指纹进行比较,或者通过在公共存储库中查找指纹来验证指纹。

然后,假设证书文件名(例如suecert)在String certName中,则接收方的验证程序(经过修改的VerSig)可以通过以下方法导入证书并从中提取公钥。

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

确保数据机密性

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