RSA算法实现数据签名与验签例子。一般应用步骤:
1.调用generateKeyPair方法,生成密钥对,获取公钥和私钥。如:pair.getPublic()和pair.getPrivate()。
2.公钥和私钥转为二进制数据表示。举例2种方式转换为字节数组。
第一种:使用java的序列化。
优点是因为公钥和私钥对象都实现了Serializable接口,所以可以直接序列化方式转为二进制。并且转换回来肯定是一致的,不用关心PublicKey和PrivateKey实现的具体细节。
缺点是后面使用签名和验签时都必须使用java语言开发,才能反序列化还原为对应的公钥和私钥。即如果不确定密钥对使用者开发语言,则这种方式不合适。
第二种:使用publicKey.getEncoded()和privateKey.getEncoded()获取对应的编码数据。
优点是:获取到的编码数据和算法有关,可以不限制使用的编程语言,只要按照算法原理就可以还原为对应的公钥和私钥。
缺点是:需要熟悉相关编程语言关于RSA算法才能还原为对应的公钥和私钥。
总结,如果确认公钥和私钥使用者都是使用java开发,那可以使用第一种,简单。否则使用第二种。
3.持久化保存。举例2种方式。
第一种:使用字符串保存。
优点是:使用时可以直接写入代码中或者配置文件中,不用单独写代码读取文件的方式获取。可以少写代码。
缺点是:需要知道字符串解码的方式才能正确的转为字节数据,虽然一般默认是Base64格式,但是每个人习惯又可能不一样,比如都转为大小写什么的。
第二种:使用文件保存。
优点是:可以直接读取到对应的字节数组,不用考虑base64解码等问题。
缺点是:单独写代码读取文件获取数据。
总结,这一步没有特别要求的话都可以,看个人喜欢了。
具体例子 ,可以看注释和main方法demo。
package com.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.security.SecureRandom;
/***
* RSA算法实现数据签名与验签例子。一般应用步骤:
* 1.调用generateKeyPair方法,生成密钥对,获取公钥和私钥。如:pair.getPublic()和pair.getPrivate()。
* 2.公钥和私钥转为二进制数据表示。举例2种方式转换为字节数组。
* 第一种:使用java的序列化。
* 优点是因为公钥和私钥对象都实现了Serializable接口,所以可以直接序列化方式转为二进制。并且转换回来肯定是一致的,不用关心PublicKey和PrivateKey实现的具体细节。
* 缺点是后面使用签名和验签时都必须使用java语言开发,才能反序列化还原为对应的公钥和私钥。即如果不确定密钥对使用者开发语言,则这种方式不合适。
* 第二种:使用publicKey.getEncoded()和privateKey.getEncoded()获取对应的编码数据。
* 优点是:获取到的编码数据和算法有关,可以不限制使用的编程语言,只要按照算法原理就可以还原为对应的公钥和私钥。
* 缺点是:需要熟悉相关编程语言关于RSA算法才能还原为对应的公钥和私钥。
* 总结,如果确认公钥和私钥使用者都是使用java开发,那可以使用第一种,简单。否则使用第二种。
* 3.持久化保存。举例2种方式。
* 第一种:使用字符串保存。
* 优点是:使用时可以直接写入代码中或者配置文件中,不用单独写代码读取文件的方式获取。可以少写代码。
* 缺点是:需要知道字符串解码的方式才能正确的转为字节数据,虽然一般默认是Base64格式,但是每个人习惯又可能不一样,比如都转为大小写什么的。
* 第二种:使用文件保存。
* 优点是:可以直接读取到对应的字节数组,不用考虑base64解码等问题。
* 缺点是:单独写代码读取文件获取数据。
* 总结,这一步没有特别要求的话都可以,看个人喜欢了。
*
* @author xpl 2024-10-26
*
*/
public class SignatureExample {
private static KeyPair pair;
private static PublicKey publicKey;
private static PrivateKey privateKey;
//publicKeyStr和privateKeyStr 通过main方法的第一步打印结果,持久化保存,方便后面测试。
private static String publicKeyStr = "rO0ABXNyABRqYXZhLnNlY3VyaXR5LktleVJlcL35T7OImqVDAgAETAAJYWxnb3JpdGhtdAASTGphdmEvbGFuZy9TdHJpbmc7WwAHZW5jb2RlZHQAAltCTAAGZm9ybWF0cQB+AAFMAAR0eXBldAAbTGphdmEvc2VjdXJpdHkvS2V5UmVwJFR5cGU7eHB0AANSU0F1cgACW0Ks8xf4BghU4AIAAHhwAAABJjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI1sbv3EoQjylFZJptKcJYBVRFblprZEX7RF1jiIbrtP5Kw8s2HqrW3GHTR9kk4j4KOUNYA+KKo30Kd7eb78EkGiYPFwgaPvA8tHau+PRv4qNeHy08CKIuGuHIQmTJICbnqVaThUSiwgtbjXqFbKWmoLSNunl7JHYqZ2qjwwnFXadFrPoC2n2B3VuVzy/y1A71HESESOkIux6FwdALO6xGD2TXIOA+1OEYxTod5yWtnDtb2kR5TXgMo19CgaqgpjBgyIQd9XoZ9adA3NWINKmIQkujr3cJBv/mEr+RVnsxy6yi8jRXmvmCz6quOFkudhNb4PHL9qSk9cfIAnNBs7EIcCAwEAAXQABVguNTA5fnIAGWphdmEuc2VjdXJpdHkuS2V5UmVwJFR5cGUAAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AAZQVUJMSUM=";
private static String privateKeyStr = "rO0ABXNyABRqYXZhLnNlY3VyaXR5LktleVJlcL35T7OImqVDAgAETAAJYWxnb3JpdGhtdAASTGphdmEvbGFuZy9TdHJpbmc7WwAHZW5jb2RlZHQAAltCTAAGZm9ybWF0cQB+AAFMAAR0eXBldAAbTGphdmEvc2VjdXJpdHkvS2V5UmVwJFR5cGU7eHB0AANSU0F1cgACW0Ks8xf4BghU4AIAAHhwAAAEwTCCBL0CAQAwDQYJKoZIhvcNAQEBBQAEggSnMIIEowIBAAKCAQEAjWxu/cShCPKUVkmm0pwlgFVEVuWmtkRftEXWOIhuu0/krDyzYeqtbcYdNH2STiPgo5Q1gD4oqjfQp3t5vvwSQaJg8XCBo+8Dy0dq749G/io14fLTwIoi4a4chCZMkgJuepVpOFRKLCC1uNeoVspaagtI26eXskdipnaqPDCcVdp0Ws+gLafYHdW5XPL/LUDvUcRIRI6Qi7HoXB0As7rEYPZNcg4D7U4RjFOh3nJa2cO1vaRHlNeAyjX0KBqqCmMGDIhB31ehn1p0Dc1Yg0qYhCS6OvdwkG/+YSv5FWezHLrKLyNFea+YLPqq44WS52E1vg8cv2pKT1x8gCc0GzsQhwIDAQABAoIBAD7L6cLkBy9ua4CfJqS5Mgu4kOZXBf5EAQ0Ov48NwcGJubvjHjH53zVxtXEj2w24b5yjCFigATWIIC3mLYqo6kBbplk/AEm3LYObfDfQcpEUVCW2yeDiVZfJVJ83VGO3EbB73KNnBiJvCdIAbJ7RLnGM/jaOxdSjTwIkifW9DgYul9aFLmDUcCFaqOIjfVpG5M6rk7DcrZSrZoAlYI3aUkXohMjbxAVg6N6YYBHtLqKMAHUp3V07VDzzV9eHZ5ewUv+r913v1uxc9OrcrsRg607PK653BXvZ3QgBfcLa5vNMnugPQ2B/xZxFaGySN4H9GBjzDU0A3e03tc/bVvNnAQ0CgYEAw4BTJNjxjDx+VEJOXExDtU3C6OxT0SLTNwe0w4SBq1QoatQ272a8fco6c3cdiDRX1CzQ4JEp9jV9MTYqxEInDU49ktKnH3RRjyMYaMxAbezVrxjSujFkhO+N5+hFmVwBz1FvfRwmKrJCgmhplUM/St6QABfqD+23eAiTBNSS8m0CgYEAuTAOM8QzCQUL1+YyJ793NNsHjJoTsYd3ZClBC9FIyC9f0HA9bTv96mn65VLPVKkwlG+KBEm+vowfMT6FwU7+TWXZs8mj/K3bgwvZVnAtbGrw1v8E6mzBpnEvqmEPoN6/F5UjTddqOS9EOCzTvVCgCC0Ahzwt3+cK6NtYbXuJVkMCgYEAiiegGfT5VXnWaGLnD1CRtIj8SS/g13T9v/DBLD3yiImTelL3c1ytrVMA1WICy6L5wiXdgiTze1s3WlSGCmUZRczZtG0Kzy6bCETr1fspOMVaCD/JguyutUa4iH7a2FJtsP1ChS4kRp3sZE2291UrYBRBvN8peBIsSjX89zfllE0CgYA3hmMbvl4W/D5a05iCNnRFks6mp6Pz/Mwdagkow+1wqFOwKZchY3V0jaY+kB++IKSCPRdCxeG9PEI1/6sXFNpG1pz5vSHAJ1jD6A+YTTYxZbckn34X09gUUaebDFMlRuRA6+ST+FzPHI710OsDvmLdULDx22dd1OZ+fxX6ban2BQKBgCheTnussmkc72m5sRbq6wS/dL5i8fkvosvFiezaQ8FGqhwCcP0GpLVlNXypY3TB4JpifbX/b2ge2FWOTyvSXjw+hwsorN8CgHVt2oKpkMfQw5vsTvB/RfnfNKCqvEid1MlHZbPQ3jlZxN1vkLIU1UMRaZ0UMtOg4SLEtSFLUH7FdAAGUEtDUyM4fnIAGWphdmEuc2VjdXJpdHkuS2V5UmVwJFR5cGUAAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AAdQUklWQVRF";
private static String data = "hello world";//签名的原始数据
private static byte[] signBytes;
//signStr 通过main方法第2步签名打印获取,持久化保存,方便后面测试。
private static String signStr = "GG47Yix1bPZ4VmnRBzcYGqqQBEeRFKD/m3ZmosdclS66/Hqz6jourvY6D/RVH7rpSKc3eZd+t9D4uJZeev6N+gGhtx57tsVfyZHn/JMiwpeh2RPcFOSP+vjLZPXoowN0xv9XD/3iqvgmPn/NN31k/yiZe6marfgHvelgpWT0a+c/mEpPn16jZ632YMGIJrNlED4UGoax062EZH3zqobwTe62DkiGvP6CikUxjxAEkaBecGYmRaN7+R59ytWK4nYdcJmnTarn4w+GB/S1fe7+K9Ewmz+8BgTOtbwZc3wZrHCzORnqAuzEmVO4zTI2wePWuuFNtpdLYKVSftDa8vFX/A==";
public static void main(String[] args) throws Exception {
/*
//1. 生成密钥对,生成密钥对,获取公钥和私钥
pair = generateKeyPair();
publicKey = pair.getPublic();
privateKey = pair.getPrivate();
//使用序列化+base64的方法持久化密钥对
publicKeyStr = writeObject(publicKey);
privateKeyStr = writeObject(privateKey);
//通过U盘或者微信、邮件等其他方式分发公钥给服务器B。
System.out.println("公钥:" + publicKeyStr);
//通过U盘或者微信、邮件等其他方式分发私钥给服务器A。
System.out.println("私钥:" + privateKeyStr);
*/
//2. 签名。服务器A使用私钥对给定的数据进行签名。获得签名后的字节数组。为了方便传输,一般将字节数组转为字符串。
data = "hello world";
privateKey = readObject(privateKeyStr);
signBytes = sign(data.getBytes("UTF-8"), privateKey);
signStr = Base64.getEncoder().encodeToString(signBytes);
System.out.println("signStr:" + signStr);
//3. 验签。服务器B使用公钥对给定的数据进行验签。其中data和signStr是通过请求获取到的参数。可能是服务器A传过来的,也可以是黑客或者未知用户传过来的。
data = "hello world";//待验证的字符串
signBytes = Base64.getDecoder().decode(signStr);
publicKey = readObject(publicKeyStr);
boolean pass = verify(data.getBytes("UTF-8"), signBytes, publicKey);
//打印验签结果。服务器A按照上签名传过来的数据,那么结果应该打印验证通过,否则的话,打印验证不通过。
System.out.println(pass ? "验证通过" : "验证不通过");
}
public static void test() throws Exception {
// 初始化密钥对生成器
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048, new SecureRandom());
// 生成密钥对
KeyPair pair = keyGen.generateKeyPair();
// 更新要签名的数据
String data = "data to sign";
// 签名
byte[] signature = sign(data.getBytes(), pair.getPrivate());
// 验证签名是否有效
boolean isValid = verify(data.getBytes(), signature, pair.getPublic());
System.out.println("Signature is " + (isValid ? "valid" : "invalid"));
}
public static String privateKeyToString(PrivateKey privateKey) throws Exception {
// 将PrivateKey转换为字节数组
byte[] privateKeyBytes = privateKey.getEncoded();
// 使用Base64进行编码
String base64Encoded = Base64.getEncoder().encodeToString(privateKeyBytes);
return base64Encoded;
}
public static PrivateKey stringToPrivateKey(String base64Encoded) throws Exception {
// 使用Base64进行解码
byte[] privateKeyBytes = Base64.getDecoder().decode(base64Encoded);
// 构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// 获取KeyFactory对象
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 生成PrivateKey对象
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
// 将PublicKey转换为字符串
public static String publicKeyToString(PublicKey publicKey) throws Exception {
// 将PrivateKey转换为字节数组
byte[] publicKeyBytes = publicKey.getEncoded();
// 使用Base64进行编码
String base64Encoded = Base64.getEncoder().encodeToString(publicKeyBytes);
return base64Encoded;
}
// 将字符串转换回PublicKey对象
public static PublicKey stringToPublicKey(String publicKeyStr) throws Exception {
// 使用Base64进行解码
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
// 构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(publicKeyBytes);
// 获取KeyFactory对象
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 生成PrivateKey对象
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
/***
* java对象序列化使用base64编码为字符串保存
* @param obj
* @return
* @throws IOException
*/
public static String writeObject(Serializable obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
/***
* 字符串使用base64解码后,反序列化为java对象
* @param <T>
* @param str
* @return
* @throws Exception
*/
public static <T> T readObject(String str) throws Exception {
byte[] bytes = Base64.getDecoder().decode(str);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
}
/***
* 生成密钥对
* @return
* @throws NoSuchAlgorithmException
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
// 初始化密钥对生成器
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048, new SecureRandom());
// 生成密钥对
KeyPair pair = keyGen.generateKeyPair();
return pair;
}
/***
* 签名
* @param data
* @param privateKey
* @return
* @throws Exception
*/
public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
/***
* 验签
* @param data
* @param signature
* @param publicKey
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
}