Java Digital Signature

2025年5月8日 | 阅读14分钟

数字签名是一种验证数字消息和文档的权威性的机制。它因提供比其他签名更高的安全性而备受欢迎。在Java中,JDK安全API用于创建和实现数字签名。在本节中,我们将讨论数字签名机制,并**在Java程序中实现数字签名机制**。

Java Digital Signature

数字签名

数字签名是一种用于签署文档、邮件、消息等的电子签名。它验证消息或文档的真实性完整性。它与手写签名、印章或戳记相同。它广泛用于验证数字消息、财务文件、身份证件等。

简而言之,我们可以说它确保了以下几点:

  • 完整性:它确保消息或文档在传输过程中不会被篡改。
  • 真实性:消息的作者确实是其声称的那个人。
  • 不可否认性:消息的作者事后无法否认他是消息的来源。

数字签名由目录服务器使用,该服务器维护信息的完整性。当我们对要传输的信息应用加密消息摘要时,信息的接收者可以轻松确定信息在传输过程中没有被篡改。

用于篡改检测和身份验证的功能称为单向哈希。它也称为消息摘要

单向哈希只不过是一个固定长度的长数字。它具有以下特征:

  • 哈希值是唯一的
  • 数据的任何更改(即使修改或更改单个字符)都会产生不同的值。
  • 哈希数据的内容不能用于其他目的。因此,它被称为单向哈希

因此,公钥私钥是数字签名数据的关键部分。签名应用程序不加密实际信息或数据,而是生成信息或数据的单向哈希。之后,它使用私钥加密哈希。加密的哈希以及其他信息(如哈希算法)被称为数字签名。下图描绘了这一点。

Java Digital Signature

术语

数字签名中使用了以下三个主要的专业术语。

公钥基础设施(PKI):这是一组(硬件、软件和其他资源)模块,用于安全地管理数字签名。它包括一对密钥,即公钥私钥

公钥由所有需要验证签名的人共享。而私钥根本不共享。它仅由签名者用于数字签名消息或文档。

证书颁发机构:这些是受信任的组织,它们在确保密钥安全和数字证书方面得到广泛认可。

数字证书:数字证书包含公钥,并指明与该密钥关联的身份。通常,证书由受信任的机构颁发,有效期为一段时间。

数字签名优势

  • 增强安全性
  • 独立验证
  • 提供高标准
  • 法律合规
  • 全球接受
  • 节省时间
  • 节省成本
  • 可追溯性

数字签名用途

数字签名用于以下领域:

  • 政府部门
  • 制造业
  • 医疗保健
  • 金融服务
  • 加密货币

数字签名工作原理

数字签名基于非对称加密。它也称为公钥加密。有许多公钥算法。但其中,最常用的是RSA(Rivest-Shamir-Adleman)和SHA(Secure Hash Algorithms)。这两种算法都用于安全的数据传输。这些算法会生成一个数学上链接的密钥对,一个是公钥,另一个是私钥。

创建数字签名的个人使用私钥来加密与签名相关的数据。接收数字签名数据的人使用签名者的公钥,这是解密数据的唯一方法。

最后,比较两个加密哈希值,以检查其真实性。如果匹配,则文档有效

如果接收者无法使用签名者的公钥打开文档或消息,则表示文档或签名存在问题。

数字签名机制要求所有各方相信创建签名的人会保密私钥。如果其他人访问了私钥,那么该方就可以以私钥持有者的名义创建欺诈性的数字签名。

Java Digital Signature

发送带数字签名的消息

从技术上讲,数字签名是消息或文档的加密哈希。这意味着数字签名会从消息生成哈希。之后,生成的哈希会使用私钥进行加密。我们可以为此使用SHARSA哈希算法。

当我们发送消息时,加密的哈希和相应的密钥也会与消息一起发送,这被归类为带有数字签名的消息。

接收带数字签名的消息

当接收者收到带有数字签名的消息时,接收者会从消息中生成一个新的哈希。之后,它使用公钥解密接收到的哈希。最后,它比较两个哈希。如果它们相等,则数字签名已验证。

这里需要注意的是,我们只加密了消息哈希,而不是整个消息。因为数字签名仅确保消息在传输过程中未被篡改。下图演示了这一点。

Java Digital Signature

数字签名流程

假设X(发送者)向Y(接收者)发送一条消息。X 计算消息的哈希值,并附加一个他想用私钥与消息一起发送的签名。

在接收者(Y)端,消息再次生成哈希,并使用 X 的公钥解密签名。之后,比较哈希值。如果在接收者(Y)端哈希值匹配,则签名已验证并确认文档有效。同时,也确保了消息在此期间未被篡改。

注意:在继续本节之前,请确保您已充分了解哈希算法。

在Java中实现数字签名

生成数字签名

Java 提供了JDK 安全 API,允许我们处理数字签名。要生成数字签名,我们必须遵循以下步骤:

  • 创建初始程序结构
  • 生成公钥和私钥
  • 签名数据
  • 将签名和公钥保存在文件中
  • 编译并运行程序

让我们按照步骤进行。

数字签名 Java 程序

创建初始程序结构

GenerateDigitalSignature.java

生成公钥和私钥

创建密钥对生成器

如上所述,数字签名需要私钥。此外,还需要相应的公钥来验证签名。但有时密钥对已存在于文件中。如果不存在,我们需要生成它。

我们可以使用 KeyPairGenerator 类来生成密钥对。它生成 1024 位长度的密钥。

我们调用 KeyPairGenerator 类的 getInstance() 方法。getInstance() 方法有两种形式。两者都有两个参数,第一个参数是算法,第二个参数是提供程序(String 类型)。

其中DSA(数字签名算法)是要使用的算法,SUN是 JDK 内置的默认提供程序。

现在,我们将初始化密钥对生成器。

初始化密钥对生成器

所有密钥对生成器都提供了密钥大小随机性的概念。KeyPairGenerator 类的 initialize() 方法接受这两个参数。

对于 DSA,密钥大小为 1024。所以,我们将密钥大小设置为1024。另一个参数随机性必须是 SecureRandom 类的实例。它提供了一个加密强随机数生成器(RNG)。它使用内置 SUN 提供程序提供的 SHA1PRNG 算法。

Java 8 提供了一系列已知的强SecureRandom。它属于 java.security.Security 类的 securerandom.strongAlgorithms 属性。因此,我们也可以使用 SecureRandom.getInstanceStrong() 方法,因为它获取已知强算法的实例。

最后,生成一对密钥。

生成密钥对

使用 KeyPair 类,我们生成公钥和私钥。

下一步是签名数据。

签名数据

Java 提供了 Signature 类,可用于创建数字签名。

获取签名对象

在上面的语法中,需要注意的是,我们提到了签名算法名称以及签名算法使用的消息摘要算法,即SHA1withDSA。其中SHA-1消息摘要算法,DSA签名算法。组合起来,这是一种使用 SHA-1 消息摘要算法来指定 DSA 算法的方法。

注意:在指定签名算法名称时,还应包含签名算法使用的消息摘要算法的名称。SHA1withDSA 是指定 DSA 签名算法的一种方式,使用了 SHA-1 消息摘要算法。

初始化签名对象

签名对象在使用前必须初始化。用于签名的初始化方法需要一个私钥。因此,我们将使用上面创建的PrivateKey对象的(priv)。

向签名对象提供要签名的数据

它使用要签名的数据。为了提供数据,我们使用 Signature 类提供的 update() 方法。

生成签名

当我们向 Signature 对象提供了所有数据后,它允许我们为此数据生成数字签名。

在下一步,我们将保存签名。

将签名和公钥保存在文件中

在上一步中,我们生成了签名字节。在此步骤中,我们将签名和公钥都保存在两个单独的文件中,以便可以与他人共享。

保存签名

我们将使用以下代码将签名保存在名为sig的文件中。

保存公钥

在这里,我们将保存编码的公钥。我们通过使用 getEncoded() 方法来获取编码的密钥。它返回编码后的字节。我们将相同的字节存储在文件中。

编译并运行程序

完成以上所有步骤后,我们得到以下源代码。

GenerateDigitalSignature.java

请记住:不要忘记指定要签名​​的文件名。我们使用了文件

输出

Java Digital Signature

当我们执行程序时,它会在指定位置生成两个名为 publickey.txtsignature.txt 的文件。

Java Digital Signature

让我们看看文件里面的内容。

digital.txt

Java Digital Signature

publickey.txt

Java Digital Signature

signature.txt

Java Digital Signature

我们看到两个文件都包含人类无法读取的加密数据。

验证数字签名

现在我们将验证上面生成的签名。为了验证签名,请遵循以下步骤:

  • 准备初始程序结构
  • 输入和转换编码的公钥字节
  • 输入签名字节
  • 验证签名
  • 编译并运行程序

让我们按照步骤进行。

准备初始程序结构

创建一个程序,其中我们将实现验证签名的逻辑。我们创建了一个名为 VerifyDigitalSignature 的 Java 文件。

VerifyDigitalSignature.java

在上面的代码片段中,我们导入了一个名为 java.security.spec 的包,该包提供了 X509EncodedKeySpec 类。

输入和转换编码的公钥字节

在此步骤中,我们将导入编码的公钥字节并将其转换为公钥。因此,为了读取公钥,请使用以下代码片段。

上面的代码将字节数组转换为编码的公钥字节。现在,从其编码实例化一个 DSA 公钥。为此,我们可以使用 KeyFactory 类。它提供了不透明密钥(密钥类型)和密钥规范之间的转换。

不透明密钥使用 X509 标准,该标准用于确定算法名称、格式名称和编码的密钥字节。请注意,它不提供密钥材料。

为此,我们需要一个密钥规范,可以通过使用以下代码来实现。假设密钥是根据 X509 标准编码的。

现在,我们将使用 KeyFactory 类的对象来转换密钥。请注意,该对象必须能够处理DSA密钥。

最后,从 KeyFactory 类的对象生成公钥规范。

输入签名字节

从指定的文件中,将签名字节作为 CLI 输入。

验证数字签名

为验证初始化签名对象

要验证签名,我们需要 Signature 类的一个对象。它使用与生成签名时相同的签名算法。

初始化 Signature 类对象。用于验证的初始化方法需要一个公钥对象(pubkey)作为参数。

向签名对象提供要验证的数据

为签名对象提供签名已生成的所有的相关数据。

验证签名

我们已经向 Signature 对象提供了所有相关数据。现在,我们可以验证数字签名了。

完成所有步骤后,我们得到以下源代码。

VerifyDigitalSignature.java

现在,编译并运行程序。

编译并运行程序

在运行上述程序时,在控制台中指定三个参数:

  • 包含编码公钥字节的文件名。
  • 包含签名字节的文件名。
  • 生成数字签名的文件数据。

运行程序后,我们得到以下输出。

输出

Java Digital Signature

我们看到签名已验证

让我们对生成数字签名的文件进行一些修改。我们从 digital.txt 文件中删除了最后一个单词。删除单词before后,文件如下所示。

digital.txt

Java Digital Signature

现在,仅运行 VerifyDigitalSignature.java 文件,看看会发生什么。

Java Digital Signature

从上面的输出中,我们可以观察到,每次我们更改或修改数据时,密钥都会改变,并且签名验证会返回false

在上面的程序中,我们使用 SHA-1 和 RSA 算法来生成和验证数字签名。类似地,我们也可以使用其他非对称算法来生成和验证数字签名。

使用 RSA 和 SHA256 算法生成和验证数字签名

让我们创建另一个 Java 程序,但在这个程序中,我们将使用SHA256算法和RSA。其余的代码和概念相同。

DigitalSignature.java

输出

Signature Value:
 0B5F7239F701F38932006DA86ED1DB0CA7883F9AE5D1934D6DC1ACB474B60CB69B39D7A983778AD6979967E0C1A9C73E5DD39CF3991B9612EABE25DAF84ECFCD52C7EBE9FD27DFE0BFF426E27476D35A30C87A94B0EA2CA99812DE4A6DA10614533A05CBF37833560E898B88C4733DF4A22C2B89DE56848A59DDE7CC0BBED0F0CE086DC6CC2789511C17008CB2BA8F2B30D6394221C2414C065385E270ADC191716BAE4B34B2790355D67D4B30AA514438515B5ACDA5E4FB846BD42226DE045294DE949AA15DE8CBD6885E29B9059136DB36347AEC5833D9B60EBEA72484A5F3B79B12E4AF6A0B9D8EB0EFBDBEC892E094E1418C6249C368F4ED39FD6EFEAE9A
Verification: true