Java 实现 API 签名算法以及 EdDSA 的 Ed25519 签名算法的使用
API 签名
精简版
签名: 指在数字通信或数据交换过程中,用来保证消息完整性和来源可信度的一种特定数据结构。这个“签名”不是传统意义上的手写签名或物理印章,而是一个经过特定数学运算产生的、代表发送者对消息认可的电子印记。
API 签名算法:就是针对 请求数据(请求参数等等) 进行签名。
API签名算法如何保证确保API请求的完整性和来源的可信性呢?
- API 签名算法生成一对密钥(公钥和私钥),私钥签名后的数据通过公钥验签,配对的密钥验签的结果一定为true,除非签名的信息被篡改;
- 其中私钥用来对请求数据进行签名,使用公钥进行验签;
- 公钥可以被传递,但是私钥不能被传递;
概念版
API 签名算法是对请求数据进行签名。具体来说,签名过程是为了确保API请求的完整性和来源的可信性,防止数据在传输过程中被篡改,同时验证请求发起者拥有合法的权限。以下是签名算法对API请求数据进行签名的详细说明:
- 请求参数:
签名算法通常针对 API 请求中包含的所有关键参数进行签名。这些参数可能包括但不限于:访问令牌(Access Token)、请求方法(GET、POST等)、请求路径(URL)、查询参数、请求体(JSON、XML等格式的数据)、时间戳、nonce(一次性随机值,用于防止重放攻击)等。
参数通常按照一定的规则(如字母序、参数重要性)进行排序,确保双方(客户端和服务端)对签名数据的处理方式一致。 - 签名密钥:
签名过程需要用到一个或多个密钥。这些密钥可能是对称密钥(如HMAC-SHA256签名中使用的密钥)或非对称密钥对(如RSA、ECDSA签名中使用的私钥和公钥)。密钥通常由服务提供商分配给API使用者,或者由使用者根据服务提供商的规范自行生成,并在安全通道上传递给服务提供商。 - 签名生成:
客户端(API使用者)将排序后的请求参数拼接成一个字符串或序列化为二进制数据,然后使用指定的签名算法(如HMAC、RSA、ECDSA等)和对应的密钥对这个数据进行签名运算,生成一个固定长度的签名值(通常为一串十六进制或Base64编码的字符串)。 - 签名传递:
客户端将生成的签名值附加到API请求中,通常作为请求头的一个字段(如Authorization、X-Signature、Signature等)发送给服务端。同时,原始请求参数也随请求一同发送。 - 签名验证:
服务端收到请求后,首先提取请求头中的签名值和请求中的所有相关参数。接着,按照与客户端相同的规则重新计算这些参数的签名。如果重新计算得到的签名与接收到的签名值匹配,说明请求数据在传输过程中未被篡改,且请求来自持有正确密钥的合法客户端。
综上所述,API签名算法是对API请求数据(包括请求参数)进行签名,目的是确保请求的完整性和来源的可信性。签名过程涉及到请求参数的规范化、密钥的使用、签名值的生成与传递以及服务端的签名验证。通过签名,服务端可以有效地鉴别请求的真伪,保障API接口的安全性。
Java 使用 Ed25519 算法进行签名和验签
1.引入依赖
使用EdDSA(Edwards-curve Digital Signature Algorithm)进行签名操作,需要借助于BouncyCastle库,因为它提供了对 EdDSA 算法的支持。添加了BouncyCastle作为JCE(Java Cryptography Extension)的提供者。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>最新版本号</version>
</dependency>
2.编写 EdDSA 签名、Ed25519
步骤:
生成 公钥 和 私钥 并保存在数据库中
- 添加 BouncyCastle 作为 JCE 提供者;
- 生成密钥对;
- 获取私钥和公钥;
- 将 公钥 和 私钥 转换为 byte 数组,经过 Base64 编码之后转换为字符串,方便存储在数据库中
签名和验签:
- 从数据库中取出公钥和私钥;
- 使用私钥对请求数据进行签名,之后转发给其他请求;
- 在另外的服务器中使用公钥验签。
package com.mahua.studytest.encryption;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator;
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.security.Security;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Encrypt {
static Map <String, String> AKAndSK = new HashMap<>();
public static void main(String[] args) throws Exception {
// 添加BouncyCastle作为JCE提供者
Security.addProvider(new BouncyCastleProvider());
// 生成Ed25519密钥对
AsymmetricCipherKeyPair keyPair = generateEd25519KeyPair();
// 获取私钥和公钥
Ed25519PrivateKeyParameters privateKeyParams = (Ed25519PrivateKeyParameters) keyPair.getPrivate();
Ed25519PublicKeyParameters publicKeyParams = (Ed25519PublicKeyParameters) keyPair.getPublic();
// 将公钥和私钥转换为 byte 数组
setAccessKeyAndSecretKeyString(privateKeyParams, publicKeyParams);
// 假设传入的请求参数是 param ,对其进行签名和验签
String param = "hello world";
verifySign(privateKeyParams, publicKeyParams, param);
}
// 对请求数据(请求参数)进行签名
private static void verifySign(Ed25519PrivateKeyParameters privateKeyParams, Ed25519PublicKeyParameters publicKeyParams,String param) {
// 模仿从数据库中得到 AK 和 SK
getAccessKeyAndSecretKey();
// 将要签名的数据(请求参数)转换为 byte 数组
byte[] message = param.getBytes();
// 使用私钥对转换为 byte 数组的数据进行签名
byte[] signature = sign(message, privateKeyParams);
// 打印签名,每一次打印签名都不一样,说明生成的公钥和私钥每一次都不一样
System.out.println("Signature (hex): " + Hex.toHexString(signature));
// 验证签名
boolean isValid = verify(message, signature, publicKeyParams);
System.out.println("Signature is valid: " + isValid);
}
// 将公钥和私钥转换为 String 字符串,并保存在数据库中
private static void setAccessKeyAndSecretKeyString(Ed25519PrivateKeyParameters privateKeyParams, Ed25519PublicKeyParameters publicKeyParams) {
// 将公钥和私钥 转换为 byte 数组
byte[] privateKeyBytes = privateKeyParams.getEncoded();
byte[] publicKeyBytes = publicKeyParams.getEncoded();
// Base64 编码将 byte 数组转换成字符串,方便存储在数据库中
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKeyBytes);
String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKeyBytes);
System.out.println(privateKeyBase64);
System.out.println(publicKeyBase64);
// 模拟将公钥和私钥存放在数据库中
save(publicKeyBase64,privateKeyBase64);
}
// 模拟将公钥和私钥存放在数据库中
private static void save(String publicKeyBase64, String privateKeyBase64) {
AKAndSK.put(privateKeyBase64,publicKeyBase64);
}
// 模拟从数据库中查询公钥和私钥
private static void getAccessKeyAndSecretKey() {
// 查询数据库并获取Base64编码的私钥和公钥字符串
String retrievedPrivateKeyBase64 = "lnYFvFIaoU/d1duKJYDKqasYtdftz+FggcVLa3d5I/Y="; // 从查询结果中获取
String retrievedPublicKeyBase64 = "Uxpvc3suXC/q6Pp6ncQ7k7vIo48/JP7P+Ve6ilb2ims="; // 从查询结果中获取
// 将 公钥和私钥的 字符串 经过 Base64 解码之后存储为 byte 数组,进而反序列化为公钥和私钥的对象
byte[] retrievedPrivateKeyBytes = Base64.getDecoder().decode(retrievedPrivateKeyBase64);
byte[] retrievedPublicKeyBytes = Base64.getDecoder().decode(retrievedPublicKeyBase64);
// 反序列化为Ed25519PrivateKeyParameters和Ed25519PublicKeyParameters对象
Ed25519PrivateKeyParameters retrievedPrivateKeyParams = new Ed25519PrivateKeyParameters(retrievedPrivateKeyBytes, 0);
Ed25519PublicKeyParameters retrievedPublicKeyParams = new Ed25519PublicKeyParameters(retrievedPublicKeyBytes, 0);
}
// 添加BouncyCastle作为JCE提供者
private static AsymmetricCipherKeyPair generateEd25519KeyPair() {
Ed25519KeyPairGenerator generator = new Ed25519KeyPairGenerator();
generator.init(new Ed25519KeyGenerationParameters(null));
return generator.generateKeyPair();
}
// 使用私钥进行签名
private static byte[] sign(byte[] message, Ed25519PrivateKeyParameters privateKeyParams) {
Ed25519Signer signer = new Ed25519Signer();
signer.init(true, privateKeyParams);
signer.update(message, 0, message.length);
return signer.generateSignature();
}
// 使用 公钥 进行验签
private static boolean verify(byte[] message, byte[] signature, Ed25519PublicKeyParameters publicKeyParams) {
Ed25519Signer verifier = new Ed25519Signer();
verifier.init(false, publicKeyParams);
verifier.update(message, 0, message.length);
return verifier.verifySignature(signature);
}
}