这篇Java教程基于JDK1.8。教程中的示例和实践不会使用未来发行版中的优化建议。
课程:用于代码安全和文件交换的API和工具
这篇课程解释了数字签名、证书和密钥库存在的必要性。该课程还比较了通过命令行工具和JDK Security API来生成数字签名。接下来的两节课将演示这种工具的用法,签名代码并授予它权限和文件交换。
该课程包含以下几部分:
- 代码和文档安全
- 工具和API说明
- 使用JDK Security API 签名文档
- 使用工具来签名代码或文档
代码和文档安全
如果您以电子方式向某人发送要运行的重要文档(或多个文档)、applet或应用程序,收件人需要一种方法来验证文档或代码是否来自您,并且在传输过程中没有被修改(例如,被恶意用户拦截)。数字签名、证书和密钥库都有助于确保您发送的文件的安全性。
数字签名
数字签名的基本概念如下所述:
- 通过 keytool 命令或者Java Security API生成私钥。接着,用 jarsigner 命令或者Java Security API为文档或者代码生成数字签名。
- 将签名后的文档发送给接收人。
- 发送文档的同时把公钥也一并发送给接收人,这个公钥是与给文档生成数字签名使用的私钥对应的。
- 接收方使用公钥来验证文档来自于你并且没有被修改。
接收方在验证签名前需要确保它收到的公钥是真实的。通常,您将提供一个包含公钥的证书和证书颁发结构的密钥,该机构将确保密钥的真实性。
证书
证书一般包含:
- 公钥
- 证书所属实体(个人、公司等)的“可识别名称”的信息。该实体也叫做证书主体或证书所有者。可识别名称包括以下属性(或子集):实体的名称、组织单元、组织、城市或地区、州或省以及国家代码。
- 数字签名。证书由发行者签名,以保证所包含的公钥是证书所属实体的实际公钥。
- 证书发行方的可识别名称。
接收方检查证书是否有效的一种方法是使用发行方的公钥验证数字签名。该密钥本身可以存储在一个证书中,该证书的签名也可以通过使用下一个证书颁发者的公钥进行验证,该密钥依然可以存储在另一个证书中,依此类推。直到获取到您信任的公钥,就可以停止迭代并使用该密钥来验证证书的签名。
如果接收方无法建立信任链,还可以使用keytool -import或-printcert命令计算证书指纹。指纹是一个相对较短的数字,它惟一且可靠地标识证书。(从技术上讲,指纹是证书信息的散列值,使用消息摘要函数。)然后,接收方可以和证书所有者沟通,并将收到的证书的指纹值与发送的证书指纹值进行比较。如果指纹相同,证书就是可信的。
通过以上两种方式您可以确保证书在传输过程中没有被修改。但使用证书另一个潜在的不确定性是发送者的身份。有时证书是自签名的,就是说使用证书中与公钥对应的私钥进行签名,证书发行方与证书所有者是同一个。
自签名证书对于开发和测试应用程序非常有用。但是,在部署应用程序给用户使用之前,要从可信的第三方(称为认证机构)获得证书。您需要向CA发送一个自签名证书签名请求(CSR)。CA可能会通过检查您的驾照或其他信息来验证CSR上的签名和您的身份。验证通过之后,CA使用自己的私钥对颁发给您的证书进行签名来保证您是公钥的所有者。任何信任CA公钥的人都可以验证证书上的签名。在许多情况下,CA自身拥有一个在证书等级体系中更高一级的CA证书,这称之为证书链。
您信任的证书通常作为“受信证书”导入密钥库。然后,可以使用每个证书中的公钥来验证之前使用相应私钥生成的签名。这种检查可通过下列方式完成:
- jarsigner工具(如果文档、代码和签名出现在JAR文件中)
- Java Security API
- 在运行时系统中,尝试进行资源访问,并且拥有真实签名的策略文件授权应用程序可以对该资源进行访问。应用的类文件和签名必须在JAR文件中。
如果要将已签名的代码或文档发送给其他人,则需要向他们提供包含公钥(与签名代码/文档的私钥对应的公钥)的证书。使用 keytool -export 命令或API方法可以将证书从密钥库导出到一个文件,该文件可以发送给任何需要它的人。接收到证书的人可以使用API方法或keytool -import 命令将证书作为受信任的证书导入密钥库。
如果使用jarsigner工具为JAR文件生成签名,该工具将从密钥库检索证书及其支持的证书链。然后将它们连同签名一起存储在JAR文件中。
密钥库
私钥及其相关的公钥证书存储在密钥库中,密钥库是基于密码保护的数据库。密钥库可以包含两种类型的条目:上面讨论的可信证书条目和密钥/证书条目:每一种都包含私钥和相应的公钥证书。密钥库中的每个条目都由别名来标识。
密钥库所有者可以在密钥库中拥有多个密钥,通过别名进行访问。别名通常用角色来命名,这个角色是指密钥库所有者使用该密钥的特定场景。别名可以标识秘钥的用途。例如,别名signPersonalEmail用于标识一个条目,该条目的私钥用于签名个人电子邮件;别名signJarFiles用来标识另一个条目,该条目的私钥用于签名JAR文件。
keytool 工具可以用来
- 创建私钥以及相应的公钥证书
- 向合适的证书颁发机构发出证书请求
- 导入您联系的认证颁发结构回复的证书请求
- 将公钥证书导入受信证书密钥库
还可以使用API方法访问和修改密钥库。
工具和API说明
请注意以下与数字签名相关的工具和API的使用。
- 您可以使用JDK Security API、工具或组合来生成密钥和签名并导入证书。您可以使用这些API或工具的特性与他人安全地交换文档。
- 要使用这些工具进行文档交换,必须将文档放在JAR文件中,该文件可以由JAR工具创建。
- 如果要创建将要签名的applet代码,则需要将其放在JAR文件中。如果您想创建运行在安全管理器下的应用程序代码,那么也需要将其放在JAR文件中。您需要JAR文件的原因是当策略文件指定只有由特定实体签名的代码可以执行一个或多个操作,比如对特定文件的读写,代码被要求来自于签名的JAR文件。(术语“已签名代码”是“已签名JAR文件中出现的类文件中的代码”的缩写。)
- 为了让运行时系统检查代码签名,运行代码的人/组织首先需要将验证公钥的证书导入到密钥库中,该公钥与用于签名代码的私钥相对应。
- 为了让jarsigner工具验证JAR文件签名的真实性,接收JAR文件的人/组织首先需要将证书导入到他们的密钥库中,该证书将提供公钥(与签名代码的私钥对应的公钥)进行身份验证。
- 目前还没有用于创建证书的api。
使用JDK Security API签名文档
生成和验证签名 向您展示了如何使用JDK Security API对文档进行签名。该课展示由拥有原始文档的人执行的程序将会做:
- 生成秘钥
- 使用私钥对文档生成数字签名
- 导出公钥和数字签名
然后,它展示由数据、签名和公钥的接收者执行的另一个程序的示例。程序是这样运行的:
- 导入公钥
- 验证签名的真实性
该课还将向您展示导入和提供密钥(包括证书)的其他方法。
使用工具来签名代码或文档
代码签名并授予IT权限一课展示了如何使用Java安全工具将代码放入JAR文件中、签名并导出公钥。然后它展示了您的接收方如何使用同样的Java安全工具导入您的公钥证书,向策略文件添加一个条目,该条目将授予您的代码访问由接收方控制的系统资源所需的权限。
文件交换一课介绍了如何使用Java安全工具对文档签名,使用keytool导出公钥证书。该公钥对应于使用keytool对文档签名使用的私钥。然后,它显示了您的收件人如何通过安装您的公钥证书,再使用jarsigner工具来验证您的签名。
这两个课程有很多共同之处。在这两种情况下,代码或文件发送者的前两个步骤是:
- 使用JAR工具创建包含文档或类文件的JAR文件。
- 使用keytool -genkey 命令生成密钥(如果它们还不存在)。
接下来的两步是可选的:
- 使用keytool -certreq 命令将生成的证书签名请求发送给认证机构(CA),如VeriSign。
- 使用keytool -import 命令导入CA的响应。
接下来的两步是必要的:
- 使用jarsigner工具和前面生成的私钥对JAR文件签名。
- 使用keytool -export 命令导出公钥证书。然后将签名的JAR文件和证书提供给接收方。
在这两种情况下,已签名JAR文件和证书的接收者都应该使用keytool -import 命令将证书作为受信任证书导入。keytool 将尝试从导入的证书到密钥库中已受信任的证书之间构造一个信任链。如果失败,keytool将显示证书指纹并提示您进行验证。
如果发送的是代码,则接收方还需要修改策略文件,对导入证书中的公钥对应的私钥所签名的代码访问所需的资源予以授权。可以使用 Policy Tool 来实现这一点。
如果发送的是文档,那么接收者需要使用 jarsigner 工具验证JAR文件签名的真实性。
本课讨论两个可选步骤。接下来的两节课将介绍其他步骤,代码签名并授予IT权限和文件交换。
为公钥证书生成一个证书签名请求(CSR)
当 keytool 用于生成公钥/私钥对时,它将创建一个包含私钥和公钥自签名证书的密钥库条目。在开发和测试应用程序时,这已经足够了。
如果证书是由认证机构(CA)颁发的,那么它更有可能被其他人信任。要获得由CA签名的证书,您首先通过以下命令生成证书签名请求(CSR):
keytool -certreq -alias alias -file csrFile
这里 -alias 用于访问包含私钥和公钥证书的密钥库条目,csrFile指定此命令创建的CSR的名称。
将该文件提交给CA,如VeriSign, Inc。CA对请求者进行身份验证,然后签名并返回验证公钥的证书。通过签署证书,CA保证您是公钥的所有者。
在有些情况下,CA将返回一个证书链,每个证书都对链中前一个证书的签名者的公钥进行身份验证。
导入CA应答
向认证机构(CA)提交证书签名请求(CSR)后,需要导入CA返回给您的证书(或证书链),将密钥存储库中的原始自签名证书替换为证书链。
首先需要在密钥存储库中(或在cacerts密钥存储库文件中,如下所述)有一个“可信证书”条目来验证CA的公钥。有了这样一个条目,CA的签名就可以得到验证。也就是说,可以验证CA在证书上的签名,或者CA响应CSR发送给您的链中的最终证书上的签名。
导入CA颁发的证书作为受信证书
在导入CA应答证书之前,您需要在密钥库或cacerts文件中包含一个或多个“受信任证书”。
- 如果证书应答是一个证书链,则只需要该链的顶部证书——验证该CA公钥的“根”CA证书。
- 如果证书应答是一个证书,则需要CA颁发结构的证书(签署证书的CA)。如果该证书不是自签名的,则需要为其签名者提供一个证书,以此类推,直到自签名的“根”CA证书为止。
cacerts 文件表示CA证书的系统密钥存储库。该文件位于JRE安全属性目录java.home/lib/security。java.home是JRE安装目录。
cacerts 文件包含许多受信任的CA证书。如果您将CSR发送给这些受信任的供应商之一(例如VeriSign),不需要将供应商的根证书作为受信任证书导入密钥库;您可以到下一节,了解如何从CA应答导入证书。
来自CA的证书通常要么是自签名的,要么是由另一个CA签名的,在这种情况下,还需要一个证书来验证CA的公钥。假设ABC公司是一个CA,并且您获得了一个名为ABCCA.cer的文件,该文件自称是来自ABC的自签名证书,用于验证CA的公钥。
在将证书导入为“受信任证书”之前,要非常小心地确保该证书是有效的!首先要查看它(使用keytool -printcert 命令或使用没有-noprompt选项的 keytool -import 命令),并确保所显示的证书指纹匹配预期的指纹。您可以联系发送证书的人,并将您看到的指纹与他们提供的指纹或安全公钥存储库显示的指纹进行比较。只有当指纹相等时,才能保证证书在传输过程中没有被其他人(例如攻击者)的证书替换。如果发生了这样的攻击,并且您在导入证书之前没有检查证书,那么您将信任攻击者所签名的任何内容。
如果您相信证书是有效的,您可以通过以下命令将其添加到密钥存储库:
keytool -import -alias alias -file ABCCA.cer -keystore storefile
该命令将在密钥存储库中创建一个“受信任证书”仓库,仓库名称通过storefile指定,该仓库包含的内容来自于ABCCA.cer并通过别名alias指定它的条目别名。
导入来自于CA的证书应答
如前一节所述,一旦导入所需的可信证书,或者它们已经存在于密钥存储库或cacerts文件中,就可以导入证书应答,从而用证书链替换自签名证书。这个证书链可能是CA应答您的CSR证书链,或者是通过证书应答与已经存在于密钥库或cacerts文件中的受信证书构造的证书链。
例如,假设您将CSR发送给VeriSign。您可以通过以下方式导入证书应答,假设返回的证书在certReplyFile指定的文件中:
keytool -import -trustcacerts -keystore storefile -alias alias -file certReplyFile
证书应答通过使用密钥存储库中的可信证书进行验证,还可以选择使用在cacerts密钥存储库文件中配置的证书进行验证(如果指定了-trustcacerts选项)。使用链中更高级别的证书验证链中的每个证书。您只需要信任链中的顶级“根”CA证书。如果您不信任顶级证书,keytool将显示该证书的指纹,并询问您是否希望信任它。
通过别名指定条目的新证书链将替换旧证书链。只有提供有效的keypass(用于保护条目私钥的密码),才能替换旧链。如果没有提供密码,并且私钥密码与密钥存储库密码不同,则会提示用户输入密码。
有关生成CSR和导入证书应答的详细信息,请参阅keytool文档: