实现java的rsa加解密与签名并调用RPC微服务(微服务系列第四天)

前言

学习java的第4天 实现目标: 实现java的rsa加解密与签名并调用RPC微服务

实现RSA工具类

由于刚开始学习,本过程是边网上抄作业,边理解。

JDK1.8 的 base64

src/main/java/com/example/rsaserver/utils/JavaBase64Util.java

public class JavaBase64Util {
    public static final String UTF_8 = "UTF-8";
    public static Base64.Encoder encoder;
    public static Base64.Encoder urlEncoder;
    public static Base64.Decoder decoder;
    public static Base64.Decoder urlDecoder;

    static {
        encoder = Base64.getEncoder();
        urlEncoder = Base64.getUrlEncoder();
        decoder = Base64.getDecoder();
        urlDecoder = Base64.getUrlDecoder();
    }

    /**
     * encode
     * @param bytes
     * @return byte[]
     */
    public static byte[] encode(final byte[] bytes) {
        return encoder.encode(bytes);
    }

    /**
     * encode
     * @param string
     * @return
     */
    public static String encode(final String string) {
        final byte[] encode = encode(string.getBytes());
        try {
            return new String(encode, UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * encode
     * @param bytes
     * @return
     */
    public static String encode2String(final byte[] bytes) {
        return encoder.encodeToString(bytes);
    }

    /**
     * encode
     * @param string
     * @return
     */
    public static byte[] encode2Byte(final String string) {
        return encode(string.getBytes());
    }

    /**
     * urlEncoder
     * @param bytes
     * @return
     */
    public static byte[] urlEncode(final byte[] bytes) {
        return urlEncoder.encode(bytes);
    }

    /**
     * urlEncoder
     * @param string
     * @return
     */
    public static String urlEncode(final String string) {
        final byte[] encode = urlEncode(string.getBytes());
        try {
            return new String(encode, UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * urlEncoder
     * @param bytes
     * @return
     */
    public static String urlEncode2String(final byte[] bytes) {
        return urlEncoder.encodeToString(bytes);
    }

    /**
     * urlEncoder
     * @param string
     * @return
     */
    public static byte[] urlEncode2Byte(final String string) {
        return urlEncode(string.getBytes());
    }

    /**
     * decode
     * @param bytes
     * @return
     */
    public static byte[] decode(final byte[] bytes) {
        return decoder.decode(bytes);
    }

    /**
     * decode
     * @param string
     * @return
     */
    public static byte[] decode2Byte(final String string) {
        return decoder.decode(string.getBytes());
    }

    /**
     * decode
     * @param bytes
     * @return
     */
    public static String decode2String(final byte[] bytes) {
        try {
            return new String(decoder.decode(bytes), UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * decode
     * @param string
     * @return
     */
    public static String decode(final String string) {
        final byte[] decode = decode(string.getBytes());
        try {
            return new String(decode, UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * urlDecode
     * @param bytes
     * @return
     */
    public static byte[] urlDecode(final byte[] bytes) {
        return urlDecoder.decode(bytes);
    }

    /**
     * urlDecode
     * @param string
     * @return
     */
    public static byte[] urlDecode2Byte(final String string) {
        return urlDecode(string.getBytes());
    }

    /**
     * urlDecode
     * @param bytes
     * @return
     */
    public static String urlDecode2String(final byte[] bytes) {
        try {
            return new String(urlDecode(bytes), UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * urlDecode
     * @param string
     * @return
     */
    public static String urlDecode(final String string) {
        final byte[] decode = urlDecode(string.getBytes());
        try {
            return new String(decode, UTF_8);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

生成公私钥

src/main/java/com/example/rsaserver/utils/RSAUtils.java

public class RSAUtils {

    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
     */
    private static final int INITIALIZE_LENGTH = 1024;

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     * 
     * @return
     * @throws Exception
     */
    public static Map<String, Object> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(INITIALIZE_LENGTH);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * <p>
     * 获取私钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return JavaBase64Util.encode2String(key.getEncoded());
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return JavaBase64Util.encode2String(key.getEncoded());
    }

}

测试 公私钥生成

src/test/java/com/example/rsaserver/RsaserverApplicationTests.java

@SpringBootTest
class RsaserverApplicationTests {

	@Test
	void genKeyPair() {
		try {
			final Map<String, Object> keyPair = RSAUtils.genKeyPair();
			assertNotNull(keyPair);

			String publicKey = RSAUtils.getPublicKey(keyPair);
			String privateKey = RSAUtils.getPrivateKey(keyPair);
			assertNotNull(publicKey);
			assertNotNull(privateKey);

			System.out.printf("RSAPublicKey base64 string is %s\n", publicKey);
			System.out.printf("RSAPrivateKey base64 string is %s\n", privateKey);

			System.out.printf("RSAPublicKey byte is %s", keyPair.get("RSAPublicKey"));
			System.out.printf("RSAPublicKey byte is %s", keyPair.get("RSAPrivateKey"));

		} catch (Exception e) {
			fail(e);
		}
	}

}

运行测试代码

$ mvn test
067509788673[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.126 s - in com.example.rsaserver.RsaserverApplicationTests

测试通过,观看测试的输出,byte和base64的公私钥都已正常输出

基于 byte 的加解密

	/**
	 * <P>
	 * 私钥解密
	 * </p>
	 * 
	 * @param encryptedData
	 *            已加密数据
	 * @param privateKey
	 *            私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
		byte[] keyBytes = JavaBase64Util.decode2Byte(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, privateK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段解密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}
 
	/**
	 * <p>
	 * 公钥解密
	 * </p>
	 * 
	 * @param encryptedData
	 *            已加密数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
		byte[] keyBytes = JavaBase64Util.decode2Byte(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, publicK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段解密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}
 
	/**
	 * <p>
	 * 公钥加密
	 * </p>
	 * 
	 * @param data
	 *            源数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
		byte[] keyBytes = JavaBase64Util.decode2Byte(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		// 对数据加密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, publicK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段加密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

基于字符串加解密 上面byte的包装

    /**
     * 公钥加密
     */
    public static String encryptedDataOnJavaByPublicKey(String data, String PUBLICKEY) {
        try {
            data = JavaBase64Util.encode2String(encryptByPublicKey(data.getBytes(), PUBLICKEY));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 私钥解密
     */
    public static String decryptDataOnJavaByPrivateKey(String data, String PRIVATEKEY) {
        String temp = "";
        try {
            byte[] rs = JavaBase64Util.decode2Byte(data);
            temp = new String(RSAUtils.decryptByPrivateKey(rs, PRIVATEKEY), "UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return temp;
    }

    /**
     * 私钥加密
     */
    public static String encryptedDataOnJavaByPrivateKey(String data, String PRIVATEKEY) {
        try {
            data = JavaBase64Util.encode2String(encryptByPrivateKey(data.getBytes(), PRIVATEKEY));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 公钥解密
     */
    public static String decryptDataOnJavaByPublicKey(String data, String PUBLICKEY) {
        String temp = "";
        try {
            byte[] rs = JavaBase64Util.decode2Byte(data);
            temp = new String(RSAUtils.decryptByPublicKey(rs, PUBLICKEY), "UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return temp;
    }

测试 加密与解密

这里的字符串加密是byte加密的一个包装,所以测试字符串加密就都测试了。

	final private String plain = "待加密字符串";

	@Test
	void encodeAndDecode() {
		try {
			final Map<String, Object> keyPair = RSAUtils.genKeyPair();
			String publicKey = RSAUtils.getPublicKey(keyPair);
			String privateKey = RSAUtils.getPrivateKey(keyPair);

			// 公钥加密,私钥解密
			String enData = RSAUtils.encryptedDataOnJavaByPublicKey(plain, publicKey);
			String deData = RSAUtils.decryptDataOnJavaByPrivateKey(enData, privateKey);
			assertEquals(deData, plain);

			// 私钥加密,公钥解密
			enData = RSAUtils.encryptedDataOnJavaByPrivateKey(plain, privateKey);
			deData = RSAUtils.decryptDataOnJavaByPublicKey(enData, publicKey);
			assertEquals(deData, plain);
		} catch (Exception e) {
			fail(e);
		}
	}
$ mvn test

测试通过

签名与验签

    /**
     * <p>
     * 用私钥对信息生成数字签名,默认签名算法使用 MD5withRSA
     * </p>
     * 
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * 
     * @return
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        return sign(data, privateKey, SIGNATURE_ALGORITHM);
    }

    /**
     * 用私钥对信息生成数字签名
     * 
     * @param data
     * @param privateKey
     * @param algorithm
     * @return
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey, String algorithm) throws Exception {
        byte[] keyBytes = JavaBase64Util.decode2Byte(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(privateK);
        signature.update(data);
        return JavaBase64Util.encode2String(signature.sign());
    }

    /**
     * <p>
     * 校验数字签名,默认使用 MD5withRSA
     * </p>
     * 
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * 
     * @return
     * @throws Exception
     * 
     */
    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
        return verify(data, publicKey, sign, SIGNATURE_ALGORITHM);
    }

    /**
     * 校验数字签名
     * 
     * @param data
     * @param publicKey
     * @param sign
     * @param algorithm
     * @return
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign, String algorithm) throws Exception {
        byte[] keyBytes = JavaBase64Util.decode2Byte(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicK);
        signature.update(data);
        return signature.verify(JavaBase64Util.decode2Byte(sign));
    }

测试 签名与验签

	@Test
	void signAndVerify() {
		try {
			final Map<String, Object> keyPair = RSAUtils.genKeyPair();
			String publicKey = RSAUtils.getPublicKey(keyPair);
			String privateKey = RSAUtils.getPrivateKey(keyPair);

			// 正确的签名测试,签名内容与验证签名的内容相符
			String enSign = RSAUtils.sign(plain.getBytes(), privateKey);
			boolean ok = RSAUtils.verify(plain.getBytes(), publicKey, enSign);
			assertTrue(ok);

			// 错误的签名测试,签名内容与验证签名的内容不符合
			String errPlain = plain + "asdfasf";
			enSign = RSAUtils.sign(errPlain.getBytes(), privateKey);
			ok = RSAUtils.verify(plain.getBytes(), publicKey, enSign);
			assertFalse(ok);

			// 使用其它签名算法
			String algorithm = "SHA256withRSA";
			enSign = RSAUtils.sign(plain.getBytes(), privateKey,  algorithm);
			ok = RSAUtils.verify(plain.getBytes(), publicKey, enSign,  algorithm);
			assertTrue(ok);
		} catch (Exception e) {
			fail(e);
		}

	}

运行测试

$ mvn test

看到可以测试通过了。

bouncycastle 包

网上查找资料,发现这个包针对加解密这一块有相当多的功能,但example很旧,很多API都有变化了,示例学习价值不大了,改天研究。

把RSA加入到tars项目中

tars 接口

今天接口加入了枚举,可以比较有效的控制输出参数 src/main/resources/rsaserver.tars

module rsaserver
{
        // 公钥类型,不同功能可能都需要到公钥,如果不想统一使用同一个公钥,可以添加多种类型,供使用。
        enum KeyType
        {
                PASSWORD,
                PLAIN,
                SIGN,
                JWT
        };

        // 签名类型
        enum SignType
        {
                MD5withRSA,
                SHA1withRSA,
                SHA256withRSA
        };

	interface Cipher
	{
                // 获取公钥 用于各应用需要公钥做加解密或签名使用
                // 为了网络安全,私钥是不允许外部获取的,只能调用远程服务间接使用私钥。
                bool getPublicKey(KeyType typ, out string pubKey);

                // 生成一对新的公私钥,客户端获取后用于其它用途
                // 默认生成的是长度1024 RSA的密钥对,有其它需要这个功能可以再加入参数就可以扩展了
                // 为了简单,目前就不加参数了
                bool genKey(out string priKey, out string pubKey);

                // 使用私钥加密
                bool encodeByPrivateKey(string data, out string enStr);

                // 使用公钥加密
                bool encodeByPublicKey(string data, out string enStr);

                // 使用私钥解密
                bool decodeByPrivateKey(string data, out string plain);

                // 使用公钥解密
                bool decodeByPublicKey(string data, out string plain);

                // 生成数字签名
                bool sign(string data, SignType typ, out string signStr);

                // 验证数字签名
                bool verify(string plain, string signStr, SignType typ);
	};
};

pom.xml

添加依赖

		<dependency>
			<groupId>com.tencent.tars</groupId>
			<artifactId>tars-spring-boot-starter</artifactId>
			<version>1.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.11</version>
		</dependency>

添加插件

		<!--tars2java plugin -->
		<plugin>
			<groupId>com.tencent.tars</groupId>
			<artifactId>tars-maven-plugin</artifactId>
			<version>1.7.2</version>
			<configuration>
				<tars2JavaConfig>
					<!-- tars file location -->
					<tarsFiles>
						<tarsFile>${basedir}/src/main/resources/rsaserver.tars</tarsFile>
					</tarsFiles>
					<!-- Source file encoding -->
					<tarsFileCharset>UTF-8</tarsFileCharset>
					<!-- Generate server code -->
					<servant>true</servant>
					<!-- Generated source code encoding -->
					<charset>UTF-8</charset>
					<!-- Generated source code directory -->
					<srcPath>${basedir}/src/main/java</srcPath>
					<!-- Generated source code package prefix -->
					<packagePrefixName>com.example.rsaserver.service.</packagePrefixName>
				</tars2JavaConfig>
			</configuration>
		</plugin>
		<!--package plugin-->

指定jar名称

		<finalName>rsaserver</finalName>

配置文件

这是我为了测试生成的一对公私钥,图方便直接把公私钥都放本地配置文件中进行测试。 src/main/resources/application.properties

# rsa pub pri key config
test.key.public=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOGU7lIbniqiI4WjoF3FmY9NAMr6VYrD+V0O/TShrSmaObYbYLHZ/NcIscCpV1ibdbhCylK77O+jEtBMAh+jR8lCVMcfNUtHBWkakiMXTMBnI5ddDOmU/J4jdQxhjlqfjvhjBzEMvUx0/16HuqfwEGolr1Zs6tBTjQ0TrsYDwSJQIDAQAB
test.key.private=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI4ZTuUhueKqIjhaOgXcWZj00AyvpVisP5XQ79NKGtKZo5thtgsdn81wixwKlXWJt1uELKUrvs76MS0EwCH6NHyUJUxx81S0cFaRqSIxdMwGcjl10M6ZT8niN1DGGOWp+O+GMHMQy9THT/Xoe6p/AQaiWvVmzq0FONDROuxgPBIlAgMBAAECgYBic9h82twu1o/1GVaAPwZ4+o23bG8UO+umQmgXrY1eAwMfIhj+JJ1WurY3TIH3ON6ocrB4FBIU17YAqfzwzalU4doFrmwk5WCpLm6xgSlhAkAMj0TDOz0irinVpS8RcYIQLWKvnl391NNIP9l2cataCnDh4lA15yzrTiSVRWrzAQJBAPgQGYvwnWZUErk4f3QvqwFvxEJyPQCUfxvYp3B/aAS9fHVjFZOUUr2W/pcVJnWlPkeTdMRs7AHjJhPt2KFSf+UCQQCSpT/uSEqht6AafooDKHcHHLnRVflIfbFglf4sYHuERp6zT0pe52/7F28sXHBuztkeA7UvHVB2JVG7zsNqv6VBAkEAuxLpMS/0hAdDV4vUErsgK6UuTS3580YJ1eY94Ak1WN3Nznk6/GEPRQtqVGYO6woDPddmZ/v8wC+dt8nXZVHiQQJAJq+Tev/1OE5h3Ttum0CsjeLFHnVoyvfluE45fGmDjDS5HyKWwwyZHQtkl7ZXLtRAsMtXm/NGy7QyqLH2GY4vQQJAJagMRmIV0AlIr1UyZk2vVU9n3LDWO57a3VMxAMQ6Hi6R3yqZb2Stgo62djYCtjoqE25gAUAs2jJ8Bk7uyRbW/A==

main

src/main/java/com/example/rsaserver/RsaserverApplication.java

@SpringBootApplication
@EnableTarsServer
public class RsaserverApplication {

	public static void main(String[] args) {
		// 关闭 spring boot 自带的web服务 目前场景只用到了rpc服务
		SpringApplication app = new SpringApplication(RsaserverApplication.class);
		app.setWebApplicationType(WebApplicationType.NONE);
		app.run(args);

	}

}

配置类

src/main/java/com/example/tarsmqserver/domain/RSAConfig.java

@Component
public class RSAConfig {
    @Value("${test.key.public}")
    private String publicKey;

    @Value("${test.key.private}")
    private  String privateKey;


    public String getPublicKey() {
        return publicKey;
    }

    public String getPrivateKey(){
        return privateKey;
    }

}

tar2java 生成代码

$ mvn tars:tars2java

生成好的接口文件

src/main/java/com/example/rsaserver/service/rsaserver/CipherServant.java

@Servant
public interface CipherServant {

	 boolean getPublicKey(@TarsMethodParameter(name="typ")int typ, @TarsHolder(name="pubKey") Holder<String> pubKey);

	 boolean genKey(@TarsHolder(name="priKey") Holder<String> priKey, @TarsHolder(name="pubKey") Holder<String> pubKey);

	 boolean encodeByPrivateKey(@TarsMethodParameter(name="data")String data, @TarsHolder(name="enStr") Holder<String> enStr);

	 boolean encodeByPublicKey(@TarsMethodParameter(name="data")String data, @TarsHolder(name="enStr") Holder<String> enStr);

	 boolean decodeByPrivateKey(@TarsMethodParameter(name="data")String data, @TarsHolder(name="plain") Holder<String> plain);

	 boolean decodeByPublicKey(@TarsMethodParameter(name="data")String data, @TarsHolder(name="plain") Holder<String> plain);

	 boolean sign(@TarsMethodParameter(name="data")String data, @TarsMethodParameter(name="typ")int typ, @TarsHolder(name="signStr") Holder<String> signStr);

	 boolean verify(@TarsMethodParameter(name="plain")String plain, @TarsMethodParameter(name="signStr")String signStr, @TarsMethodParameter(name="typ")int typ);
}

tars 服务接口实现

src/main/java/com/example/rsaserver/service/rsaserver/impl/CipherServantImpl.java

@TarsServant("cipherObj")
@Component
public class CipherServantImpl implements CipherServant {

    @Autowired
    private RSAConfig rsaConfig;

    private final static Logger RSA_LOGGER = LoggerFactory.getLogger("RSA_SERVER");

    @Override
    public boolean genKey(Holder<String> priKey, Holder<String> pubKey) {
        try {
            final Map<String, Object> keyPair = RSAUtils.genKeyPair();
            String publickey = RSAUtils.getPublicKey(keyPair);
            String privatekey = RSAUtils.getPrivateKey(keyPair);
            priKey.setValue(privatekey);
            pubKey.setValue(publickey);
        } catch (Exception e) {
            return exHandler("genKey", e);
        }

        RSA_LOGGER.info("genkey() successfully call");
        return true;
    }

    @Override
    public boolean getPublicKey(int typ, Holder<String> pubKey) {
        String publicKey = rsaConfig.getPublicKey();
        KeyType convert = KeyType.convert(typ);

        // TODO 这里目前没有往下扩展,后续可以扩展完善
        // 目前所有都返回统一的公钥
        switch (convert) {
            case PASSWORD:
                pubKey.setValue(publicKey);
                break;

            case PLAIN:
                pubKey.setValue(publicKey);
                break;

            case SIGN:
                pubKey.setValue(publicKey);
                break;

            case JWT:
                pubKey.setValue(publicKey);
                break;

            default:
                pubKey.setValue(null);
                break;
        }

        if (StringUtils.isBlank(pubKey.getValue())) {
            return false;
        }
        return true;
    }

    @Override
    public boolean encodeByPrivateKey(String data, Holder<String> enStr) {
        if (StringUtils.isBlank(data)) {
            return false;
        }

        try {
            String privateKey = rsaConfig.getPrivateKey();
            String enData = RSAUtils.encryptedDataOnJavaByPrivateKey(data, privateKey);
            enStr.setValue(enData);
        } catch (Exception e) {
            return exHandler("encodeByPrivateKey", e);
        }

        RSA_LOGGER.info("encodeByPrivateKey() successfully call");
        return true;
    }

    @Override
    public boolean encodeByPublicKey(String data, Holder<String> enStr) {
        if (StringUtils.isBlank(data)) {
            return false;
        }

        try {
            String publicKey = rsaConfig.getPublicKey();
            String enData = RSAUtils.encryptedDataOnJavaByPublicKey(data, publicKey);
            enStr.setValue(enData);
        } catch (Exception e) {
            return exHandler("encodeByPublicKey", e);
        }

        RSA_LOGGER.info("encodeByPublicKey() successfully call");
        return true;
    }

    @Override
    public boolean decodeByPrivateKey(String data, Holder<String> plain) {
        if (StringUtils.isBlank(data)) {
            return false;
        }

        try {
            String privateKey = rsaConfig.getPrivateKey();
            String deData = RSAUtils.decryptDataOnJavaByPrivateKey(data, privateKey);
            plain.setValue(deData);
        } catch (Exception e) {
            return exHandler("decodeByPrivateKey", e);
        }

        RSA_LOGGER.info("decodeByPrivateKey() successfully call");
        return true;
    }

    @Override
    public boolean decodeByPublicKey(String data, Holder<String> plain) {
        if (StringUtils.isBlank(data)) {
            return false;
        }

        try {
            String publicKey = rsaConfig.getPublicKey();
            String deData = RSAUtils.decryptDataOnJavaByPublicKey(data, publicKey);
            plain.setValue(deData);
        } catch (Exception e) {
            return exHandler("decodeByPublicKey", e);
        }

        RSA_LOGGER.info("decodeByPublicKey() successfully call");
        return true;
    }

    @Override
    public boolean sign(String data, int typ, Holder<String> signStr) {
        if (StringUtils.isBlank(data)) {
            return false;
        }

        try {
            // TODO 目前只使用了默认的 MD5withRSA 需要其它的功能,后续可以添加,通过入参typ来调用不同的方法
            String privateKey = rsaConfig.getPrivateKey();
            String enSign = RSAUtils.sign(data.getBytes(), privateKey);
            signStr.setValue(enSign);
        } catch (Exception e) {
            return exHandler("sign", e);
        }

        RSA_LOGGER.info("sign() successfully call");
        return true;
    }

    @Override
    public boolean verify(String plain, String signStr, int typ) {
        if (StringUtils.isBlank(plain) || StringUtils.isBlank(signStr)) {
            return false;
        }

        // TODO 目前只使用了默认的 MD5withRSA 需要其它的功能,后续可以添加,通过入参typ来调用不同的方法
        try {
            String publicKey = rsaConfig.getPublicKey();
            boolean ok = RSAUtils.verify(plain.getBytes(), publicKey, signStr);
            RSA_LOGGER.info("verify() successfully call");
            return ok;
        } catch (Exception e) {
            return exHandler("verify", e);
        }
    }

    private boolean exHandler(String funcname, Exception e) {
        e.printStackTrace();
        RSA_LOGGER.error("{}(): {}", funcname, e.toString());
        return false;

    }

}

测试代码

src/test/java/com/example/rsaserver/CipherServantImplTest.java

@SpringBootTest
@Component
public class CipherServantImplTest {

    // 因CipherServantImpl中有 @Autowired 所以测试的时候不能使用new,
    // 必须使用 @Autowired来装载类才能读取到配置,要不然会出现空指针对错误
    @Autowired
    private CipherServantImpl imp;

    @Test
    void genKey() {
        Holder<String> priKey = new Holder<String>();
        Holder<String> pubKey = new Holder<String>();
        boolean ok = imp.genKey(priKey, pubKey);
        assertTrue(ok);
    }

    @Test
    public void getPublicKey() {
        KeyType convert = KeyType.convert(0);
        System.out.println(convert.toString());
        Holder<String> pubKey = new Holder<String>();
        boolean ok = imp.getPublicKey(0, pubKey);
        assertTrue(ok);
    }

    @Test
    public void encodeByPrivateKeyWithDecodeByPublicKey() {
        String data = "as65df4656a4sd6fa";
        Holder<String> enStr = new Holder<String>();
        boolean ok = imp.encodeByPrivateKey(data, enStr);
        assertTrue(ok);

        Holder<String> plain = new Holder<String>();
        imp.decodeByPublicKey(enStr.getValue(), plain);
        assertEquals(plain.value, data);

    }

    @Test
    public void encodeByPublicKeyWithDecodeByPrivateKey() {
        String data = "as65df4656a4sd6fa";
        Holder<String> enStr = new Holder<String>();
        boolean ok = imp.encodeByPublicKey(data, enStr);
        assertTrue(ok);

        Holder<String> plain = new Holder<String>();
        imp.decodeByPrivateKey(enStr.getValue(), plain);
        assertEquals(plain.value, data);
    }

    @Test
    public void signWithVerify() {
        String data = "as65df4656a4sd6fa";
        Holder<String> signStr = new Holder<String>();
        boolean ok = imp.sign(data, 0, signStr);
        assertTrue(ok);

        ok = imp.verify(data, signStr.getValue(), 0);
        assertTrue(ok);

    }
}

tars 配置文件

src/main/resources/example.rsaserver.config.conf

<tars>
	<application>
		enableset=N
		setdivision=NULL
		<client>
			locator=
			sync-invoke-timeout=20000
			async-invoke-timeout=20000
			refresh-endpoint-interval=60000
			stat=tars.tarsstat.StatObj
			property=tars.tarsproperty.PropertyObj
			report-interval=60000
			modulename=rsaserver.cipherJavaServer
		</client>
		<server>
			node=
			app=rsaserver
			server=Cipher
			localip=127.0.0.1
			local=tcp -h 127.0.0.1 -p 18601 -t 3000
			basepath=.
			datapath=.
			logpath=.
			loglevel=DEBUG
			logsize=15M
			log=
			config=tars.tarsconfig.ConfigObj
			notify=tars.tarsnotify.NotifyObj
			mainclass=com.qq.tars.server.startup.Main
			jvmparams=-Xms1024m -Xmx1024m
			sessiontimeout=120000
			sessioncheckinterval=60000
			tcpnodelay=true
			udpbuffersize=8192
			charsetname=UTF-8
			<rsaserver.Cipher.cipherObjAdapter>
				allow
				endpoint=tcp -h 127.0.0.1 -p 18600 -t 60000
				handlegroup=rsaserver.Cipher.cipherObjAdapter
				maxconns=200000
				protocol=tars
				queuecap=10000
				queuetimeout=60000
				servant=rsaserver.Cipher.cipherObj
				shmcap=0
				shmkey=0
				threads=100
			</rsaserver.Cipher.cipherObjAdapter>
		</server>
	</application>
</tars>

运行测试

$ mvn test -Dconfig=src/main/resources/example.rsaserver.config.conf

或用我提供的Makefile来测试 传送门

$ make test

测试结果

[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

tars-web 上传及发布

$ make build-upload

或者手动上传

上传tars接口定义文件

手动上传src/main/resources/rsaserver.tars 手动测试 查看结果 一切正常

修改day2的mqserver项目

修改mqserver.tars

因tars插件多个tars文件,共有是否生成服务接口选项,本例中同是有服务端和客户端,需要分别生成才行 src/main/resources/mqserver.tars

module mqserver
{
	interface Message
	{
                // msg 字符串内容原样发送
                bool send(string msg);

                // 用私钥对字符串进行加密,返回加密后的字符串
                bool encode(string sign, out string enStr);

                // 用私钥对字符串进行加密,并发送加密后的内容
                bool encodeWithSend(string msg);

                // 具体字符串拼接可能自己定义,是否在字符串中传输是否加密字符,或者默认约定哪些服务发送密文,哪些服务发送明文
                // if isEncode = false then
                // 对发送的原内容进行签名,之后签名字符串与原内容字符串使用 | 拼接为一个字符串后发送
                // e.g: msg = abc, signStr = AF@FSDafe23 最后发送的字符串 msg = AF@FSDafe23|abc
                // if isEncode = true then
                // 对发送的原内容先进行签名并对原内容进行加密,之后使用 | 拼接为一个字符串后发送
                // e.g: msg = abc signStr = AF@FSDafe23 enStr = ASFDHFHSGGG233f2f2fadfAGAF 最后发送的字符串 msg = AF@FSDafe23|ASFDHFHSGGG233f2f2fadfAGAF
                bool signWithSend(string msg, bool isEncode);
	};
};

生成服务接口

$ mvn tars:tars2java

复制rsaserver.tars到mqserver项目中

pom.xml 修改

			<!--tars2java plugin -->
			<plugin>
				<groupId>com.tencent.tars</groupId>
				<artifactId>tars-maven-plugin</artifactId>
				<version>1.7.2</version>
				<configuration>
					<tars2JavaConfig>
						<!-- tars file location -->
						<tarsFiles>
							<tarsFile>${basedir}/src/main/resources/mqserver.tars</tarsFile>
						</tarsFiles>
						<!-- Source file encoding -->
						<tarsFileCharset>UTF-8</tarsFileCharset>
						<!-- Generate server code -->
						<servant>true</servant>
						<!-- Generated source code encoding -->
						<charset>UTF-8</charset>
						<!-- Generated source code directory -->
						<srcPath>${basedir}/src/main/java</srcPath>
						<!-- Generated source code package prefix -->
						<packagePrefixName>com.example.tarsmqserver.service.</packagePrefixName>
					</tars2JavaConfig>
					<tars2JavaConfig>
						<!-- tars file location -->
						<tarsFiles>
							<tarsFile>${basedir}/src/main/resources/rsaserver.tars</tarsFile>
						</tarsFiles>
						<!-- Source file encoding -->
						<tarsFileCharset>UTF-8</tarsFileCharset>
						<!-- Generate server code -->
						<servant>false</servant>
						<!-- Generated source code encoding -->
						<charset>UTF-8</charset>
						<!-- Generated source code directory -->
						<srcPath>${basedir}/src/main/java</srcPath>
						<!-- Generated source code package prefix -->
						<packagePrefixName>com.example.tarsmqserver.service.</packagePrefixName>
					</tars2JavaConfig>
				</configuration>
			</plugin>
			<!--package plugin-->

生成tars客户端代码

$ mvn tars:tars2java

新生成的代码存放在 src/main/java/com/example/tarsmqserver/service/rsaserver

修改接口实现

src/main/java/com/example/tarsmqserver/service/mqserver/impl/MessageServantImpl.java

@TarsServant("messageObj")
@Component
public class MessageServantImpl implements MessageServant {
    @Autowired
    private Producer producer;

    @Autowired
    private JmqConfig jmqConfig;

    @TarsClient("example.rsaserver.cipherObj")
    CipherPrx cipherPrx;

    private final static Logger MQSERVER_LOGGER = LoggerFactory.getLogger("MQSERVER");

    /**
     * 发送消息到IBM MQ
     */
    @Override
    public boolean send(String msg) {

        if (StringUtils.isBlank(msg)) {
            return false;
        }

        try {
            String queueName = jmqConfig.getSendQueueName();
            MQSERVER_LOGGER.info("send: {} -> {}", queueName, msg);
            return producer.sendMessage(queueName, msg);
                
        } catch (Exception e) {
            return exHandler("send", e);
        }
    }

    @Override
    public boolean encode(String msg, Holder<String> enStr) {
        if (StringUtils.isBlank(msg)) {
            return false;
        }

        try {
            boolean ok = cipherPrx.encodeByPrivateKey(msg, enStr);
            MQSERVER_LOGGER.info("encode: {} -> {}", msg, enStr.value);
            return ok;
        } catch (Exception e) {
            return exHandler("encode", e);
        }

    }

    @Override
    public boolean encodeWithSend(String msg) {
        if (StringUtils.isBlank(msg)) {
            return false;
        }

        try {
            Holder<String> enStr = new Holder<String>();
            boolean ok = encode(msg, enStr);
            if (!ok) {
                return false;
            }
            return send(enStr.value);

        } catch (Exception e) {
            return exHandler("encodeWithSend", e);
        }
    }

    @Override
    public boolean signWithSend(String msg, boolean isEncode) {
        if (StringUtils.isBlank(msg)) {
            return false;
        }

        try {
            // 原内容去生成签名
            Holder<String> signStr = new Holder<String>();
            boolean ok = cipherPrx.sign(msg, SignType.MD5withRSA.value(), signStr);
            MQSERVER_LOGGER.info("signWithSend: {} -> {}", msg, signStr.value);
            if (!ok) {
                return false;
            }
            String content = signStr.getValue() + "|";

            // 是否需要对发送的内容进行加密
            if (isEncode) {
                Holder<String> enStr = new Holder<String>();

                ok = encode(msg, enStr);
                // 加密失败处理逻辑,根据业务需求不同,可以采用返回失败或不加密,直接发送明文
                if (!ok) {
                    // 这里选择直接发送明文
                    content += msg;
                } else {
                    // 加密成功,发送密文
                    content += enStr.getValue();
                }

            } else {
                // 不加密,发送明文
                content += msg;
            }
            return send(content);
        } catch (Exception e) {
            return exHandler("signWithSend", e);
        }
    }

    /**
     * 错误处理器 类里面统一一下,有空再研究一下统一错误机制
     * @param funcname
     * @param e
     * @return
     */
    private boolean exHandler(String funcname, Exception e) {
        e.printStackTrace();
        MQSERVER_LOGGER.error("{}() fail call. cause: {}", funcname, e.toString());
        return false;

    }

}

优化消息监听者服务

@Component
public class ConsumerListener {
    private final static Logger JMS_LISTENER = LoggerFactory.getLogger("JMS_LISTENER");

    /**
     * 使用JmsListener配置消费者监听的队列
     * 
     * @param receivedMsg 接收到的消息
     */

    // @JmsListener(destination = "DEV.QUEUE.1")
    @JmsListener(destination = "#{@conf.recvQueueName}")
    public void receiveQueue(String receivedMsg) {
        JMS_LISTENER.info("RECEIVE: {}", receivedMsg);
    }

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory,
            ExampleErrorHandler errorHandler) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setErrorHandler(errorHandler);
        return factory;
    }

    @Service
    public class ExampleErrorHandler implements ErrorHandler {
        @Override
        public void handleError(Throwable t) {
            // handle exception here
            JMS_LISTENER.error(t.toString());
        }
    }

    @Bean
    public JmqConfig conf() {
        return new JmqConfig();
    }
}

打包/上传/测试/观察

我使用 Makefile 打包上传 Makefile 代码 传送门

$ make build-upload

一切正常,加密和签名都测试成功了

源代码

实现java的rsa加解密与签名并调用RPC微服务(微服务系列第四天)

结语

经过几天的折腾,虽然代码没写多少行,但碰到的问题确实很多,还好都基本上解决了.这次使用了一个服务调用另外一个服务的方式,体现了分布式微服务的一些开发与使用. 今天的rsaserver进展扩展,很快就可以弄出一个密钥服务中心,专门针对加解密进行处理,又单独保存了公私钥,扩展一下还可以搞密钥链. 私钥越少人掌握就越安全.