java 使用RSA算法确保数据来源是指定用户

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

猜你喜欢

转载自blog.csdn.net/xiaozaq/article/details/143246326