业务场景

在软件产品或者源码发布后,防止产品或者源码的外流导致核心竞争力被破解,直接影响商业价值,如何对产品进行加密,对产品的保护非常的重要。
1、办公软件-下载安装后如果没有进行授权,可以无限制的复制,导致产品被滥用,资金流失。
2、源码被窃取,通过源码解读,去除加密授权的拦截或者过滤功能,导致源码流失,资金损失,核心产品外泄。
如果去管控呢?
方法一、只对外发布产品安装包或者产品运行包,不提供核心源码,如果非要提供源码,也要控制核心工具类、验证类是jar包的形式使用,而非全部代码。
方法二、增加额外的验证机制,当发现拦截器或过滤器没有相应时候,采取代码破坏机制,动态删除一些核心的配置文件,并且将破解人的远程IP,机器码,授权源码信息发送给管理端。
方法三、代码混淆,源代码保护,代码逻辑混淆,代码加密,提高代码阅读成本
所有的加密、混淆、机制管控都是操作产品或者源码被泄露的手段,但是任何的手段都可以被破解,所以在实际过程中,除了增加手段之外还要结合其他的方式进行关联,比如签订合同、软著申请等等。

产品和源码认证授权加密

产品或者源码的认证加密,我们采用RSA非对称加密进行管理。

RSA非对称加密

什么是rsa非对称加密

一般场景 服务端和移动端 数据传输加密,用AES对称加密,同一个秘钥,当一端被获取后,数据都将被获取。
对称加密算法在加密和解密时使用的是同一个秘钥;而非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
服务端-公钥加密 移动端-私钥解密。
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫做非对称加密算法。
springboot Rsa非对称加密实现-产品和源码认证授权加密 - 图2

非对称加密的优缺点

非对称加密与对称加密相比,其安全性更好:对称加密的通信双方使用相同的秘钥,如果一方的秘钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对秘钥,一个用来加密,一个用来解密,而且公钥是公开的,秘钥是自己保存的,不需要像对称加密那样在通信之前要先同步秘钥。
非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

RSA加解密工具类

  1. package com.qingfeng.util;
  2. import java.io.ByteArrayOutputStream;
  3. import java.security.Key;
  4. import java.security.KeyFactory;
  5. import java.security.KeyPair;
  6. import java.security.KeyPairGenerator;
  7. import java.security.PrivateKey;
  8. import java.security.PublicKey;
  9. import java.security.Signature;
  10. import java.security.interfaces.RSAPrivateKey;
  11. import java.security.interfaces.RSAPublicKey;
  12. import java.security.spec.PKCS8EncodedKeySpec;
  13. import java.security.spec.X509EncodedKeySpec;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. import javax.crypto.Cipher;
  17. import org.apache.commons.codec.binary.Base64;
  18. /**
  19. * <p>
  20. * rsa 加密工具
  21. */
  22. public class RSAUtils {
  23. /**
  24. * 加密算法RSA
  25. */
  26. public static final String KEY_ALGORITHM = "RSA";
  27. /**
  28. * 签名算法
  29. */
  30. public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
  31. /**
  32. * 获取公钥的key
  33. */
  34. private static final String PUBLIC_KEY = "RSAPublicKey";
  35. /**
  36. * 获取私钥的key
  37. */
  38. private static final String PRIVATE_KEY = "RSAPrivateKey";
  39. /**
  40. * RSA最大加密明文大小
  41. */
  42. private static final int MAX_ENCRYPT_BLOCK = 117;
  43. /**
  44. * RSA最大解密密文大小
  45. */
  46. private static final int MAX_DECRYPT_BLOCK = 128;
  47. /**
  48. * <p>
  49. * 生成密钥对(公钥和私钥)
  50. * </p>
  51. *
  52. * @return
  53. * @throws Exception
  54. */
  55. public static Map<String, Object> genKeyPair() throws Exception {
  56. KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
  57. keyPairGen.initialize(1024);
  58. KeyPair keyPair = keyPairGen.generateKeyPair();
  59. RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
  60. RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
  61. Map<String, Object> keyMap = new HashMap<String, Object>(2);
  62. keyMap.put(PUBLIC_KEY, publicKey);
  63. keyMap.put(PRIVATE_KEY, privateKey);
  64. return keyMap;
  65. }
  66. /**
  67. * <p>
  68. * 用私钥对信息生成数字签名
  69. * </p>
  70. *
  71. * @param data 已加密数据
  72. * @param privateKey 私钥(BASE64编码)
  73. *
  74. * @return
  75. * @throws Exception
  76. */
  77. public static String sign(byte[] data, String privateKey) throws Exception {
  78. byte[] keyBytes = decode(privateKey);
  79. PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
  80. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  81. PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
  82. Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
  83. signature.initSign(privateK);
  84. signature.update(data);
  85. return encode(signature.sign());
  86. }
  87. /**
  88. * <p>
  89. * 校验数字签名
  90. * </p>
  91. *
  92. * @param data 已加密数据
  93. * @param publicKey 公钥(BASE64编码)
  94. * @param sign 数字签名
  95. *
  96. * @return
  97. * @throws Exception
  98. *
  99. */
  100. public static boolean verify(byte[] data, String publicKey, String sign)
  101. throws Exception {
  102. byte[] keyBytes = decode(publicKey);
  103. X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
  104. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  105. PublicKey publicK = keyFactory.generatePublic(keySpec);
  106. Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
  107. signature.initVerify(publicK);
  108. signature.update(data);
  109. return signature.verify(decode(sign));
  110. }
  111. /**
  112. * <P>
  113. * 私钥解密
  114. * </p>
  115. *
  116. * @param encryptedData 已加密数据
  117. * @param privateKey 私钥(BASE64编码)
  118. * @return
  119. * @throws Exception
  120. */
  121. public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey)
  122. throws Exception {
  123. byte[] keyBytes = decode(privateKey);
  124. PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
  125. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  126. Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
  127. Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  128. cipher.init(Cipher.DECRYPT_MODE, privateK);
  129. int inputLen = encryptedData.length;
  130. ByteArrayOutputStream out = new ByteArrayOutputStream();
  131. int offSet = 0;
  132. byte[] cache;
  133. int i = 0;
  134. // 对数据分段解密
  135. while (inputLen - offSet > 0) {
  136. if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
  137. cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
  138. } else {
  139. cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
  140. }
  141. out.write(cache, 0, cache.length);
  142. i++;
  143. offSet = i * MAX_DECRYPT_BLOCK;
  144. }
  145. byte[] decryptedData = out.toByteArray();
  146. out.close();
  147. return decryptedData;
  148. }
  149. /**
  150. * <p>
  151. * 公钥解密
  152. * </p>
  153. *
  154. * @param encryptedData 已加密数据
  155. * @param publicKey 公钥(BASE64编码)
  156. * @return
  157. * @throws Exception
  158. */
  159. public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey)
  160. throws Exception {
  161. byte[] keyBytes = decode(publicKey);
  162. X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
  163. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  164. Key publicK = keyFactory.generatePublic(x509KeySpec);
  165. Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  166. cipher.init(Cipher.DECRYPT_MODE, publicK);
  167. int inputLen = encryptedData.length;
  168. ByteArrayOutputStream out = new ByteArrayOutputStream();
  169. int offSet = 0;
  170. byte[] cache;
  171. int i = 0;
  172. // 对数据分段解密
  173. while (inputLen - offSet > 0) {
  174. if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
  175. cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
  176. } else {
  177. cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
  178. }
  179. out.write(cache, 0, cache.length);
  180. i++;
  181. offSet = i * MAX_DECRYPT_BLOCK;
  182. }
  183. byte[] decryptedData = out.toByteArray();
  184. out.close();
  185. return decryptedData;
  186. }
  187. /**
  188. * <p>
  189. * 公钥加密
  190. * </p>
  191. *
  192. * @param data 源数据
  193. * @param publicKey 公钥(BASE64编码)
  194. * @return
  195. * @throws Exception
  196. */
  197. public static byte[] encryptByPublicKey(byte[] data, String publicKey)
  198. throws Exception {
  199. byte[] keyBytes = decode(publicKey);
  200. X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
  201. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  202. Key publicK = keyFactory.generatePublic(x509KeySpec);
  203. // 对数据加密
  204. Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  205. cipher.init(Cipher.ENCRYPT_MODE, publicK);
  206. int inputLen = data.length;
  207. ByteArrayOutputStream out = new ByteArrayOutputStream();
  208. int offSet = 0;
  209. byte[] cache;
  210. int i = 0;
  211. // 对数据分段加密
  212. while (inputLen - offSet > 0) {
  213. if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
  214. cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
  215. } else {
  216. cache = cipher.doFinal(data, offSet, inputLen - offSet);
  217. }
  218. out.write(cache, 0, cache.length);
  219. i++;
  220. offSet = i * MAX_ENCRYPT_BLOCK;
  221. }
  222. byte[] encryptedData = out.toByteArray();
  223. out.close();
  224. return encryptedData;
  225. }
  226. /**
  227. * <p>
  228. * 私钥加密
  229. * </p>
  230. *
  231. * @param data 源数据
  232. * @param privateKey 私钥(BASE64编码)
  233. * @return
  234. * @throws Exception
  235. */
  236. public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
  237. throws Exception {
  238. byte[] keyBytes =decode(privateKey);
  239. PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
  240. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  241. Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
  242. Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  243. cipher.init(Cipher.ENCRYPT_MODE, privateK);
  244. int inputLen = data.length;
  245. ByteArrayOutputStream out = new ByteArrayOutputStream();
  246. int offSet = 0;
  247. byte[] cache;
  248. int i = 0;
  249. // 对数据分段加密
  250. while (inputLen - offSet > 0) {
  251. if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
  252. cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
  253. } else {
  254. cache = cipher.doFinal(data, offSet, inputLen - offSet);
  255. }
  256. out.write(cache, 0, cache.length);
  257. i++;
  258. offSet = i * MAX_ENCRYPT_BLOCK;
  259. }
  260. byte[] encryptedData = out.toByteArray();
  261. out.close();
  262. return encryptedData;
  263. }
  264. /**
  265. * <p>
  266. * 获取私钥
  267. * </p>
  268. *
  269. * @param keyMap 密钥对
  270. * @return
  271. * @throws Exception
  272. */
  273. public static String getPrivateKey(Map<String, Object> keyMap)
  274. throws Exception {
  275. Key key = (Key) keyMap.get(PRIVATE_KEY);
  276. return encode(key.getEncoded());
  277. }
  278. /**
  279. * <p>
  280. * 获取公钥
  281. * </p>
  282. *
  283. * @param keyMap 密钥对
  284. * @return
  285. * @throws Exception
  286. */
  287. public static String getPublicKey(Map<String, Object> keyMap)
  288. throws Exception {
  289. Key key = (Key) keyMap.get(PUBLIC_KEY);
  290. return encode(key.getEncoded());
  291. }
  292. /**
  293. * base64 加密
  294. * @param bytes
  295. * @return
  296. * @throws Exception
  297. */
  298. public static String encode(byte[] bytes) throws Exception {
  299. return new String(Base64.encodeBase64(bytes));
  300. }
  301. /**
  302. * base64 解密
  303. * @param base64
  304. * @return
  305. * @throws Exception
  306. */
  307. public static byte[] decode(String base64) throws Exception {
  308. return Base64.decodeBase64(base64.getBytes());
  309. }
  310. /**
  311. * @Description: 解密
  312. * @Param: [data, privateKey]
  313. * @return: java.lang.String
  314. * @Author: anxingtao
  315. * @Date: 2019-3-13 11:06
  316. */
  317. public static String decrypt(String data, String privateKey) throws Exception {
  318. byte[] pubdata = decryptByPrivateKey(decode(data), privateKey);
  319. return new String(pubdata, "UTF-8");
  320. }
  321. /**
  322. * @Description: 加密
  323. * @Param: [data, publicKey]
  324. * @return: java.lang.String
  325. * @Author: anxingtao
  326. * @Date: 2019-3-13 11:10
  327. */
  328. public static String encrypt(String data, String publicKey) throws Exception {
  329. byte[] pubdata = encryptByPublicKey(data.getBytes("UTF-8"), publicKey);
  330. String outString = new String(encode(pubdata));
  331. return outString;
  332. }
  333. /**
  334. * @title encrypriva
  335. * @description 私钥加密
  336. * @author Administrator
  337. * @updateTime 2021/8/11 16:31
  338. */
  339. public static String encrypk(String data, String privateKey) throws Exception {
  340. byte[] resdata = encryptByPrivateKey(data.getBytes("UTF-8"), privateKey);
  341. String outString = new String(encode(resdata));
  342. return outString;
  343. }
  344. /**
  345. * @title decrypk
  346. * @description 公钥解密
  347. * @author Administrator
  348. * @updateTime 2021/8/11 16:32
  349. */
  350. public static String decrypk(String data, String publicKey) throws Exception {
  351. byte[] resdata = decryptByPublicKey(decode(data), publicKey);
  352. return new String(resdata, "UTF-8");
  353. }
  354. public static void main(String[] args) throws Exception {
  355. // String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALbGpPkikVf51rzHHr5rCmtIjpsq9Pked0W5xu5Khf4fQEl7utYahfC3dleFaCyLkD39Q1PpyOrQm7YQPSxkHR7Bn/w/CA93EYNKXG1cAZjDr/ZJAd6xUY2K4GkriWB9v4OeCTThkKwfharJJW270No5VT49ktKAHKkug/WnCUN7AgMBAAECgYEAlxPNK20qTFjj6biBLg5WV2VrEtFIGl7XYdf0meUZqnr0bYkLX4we6GENPby05hUaTlL4gvT8MTPrcWss1XOPKQYRrSjLBZmmCiYe+SDxx0ZPmNN1db86X0ahQsNzxAVkTyIKKVW1SxwyvIOSN5H71yyBzZj/yG0qymj8H0hBVFkCQQDijlLZ9E7pGT/2HLitxEyi02kIjlsgLtjmk8NP8if5eCspmKXaw3xH2j3BLOGDGZUjeQGZcyOOHdGnNkbUVSdtAkEAzoe6ugzr63r70CwbWAEBQLRUfX4i8fiLazlCybhVAMEKjbKTdcAUFLfKNmharPHE/dlHLja/dReaLMKB7l69hwJABcM/AkI/m5hD0zvJysm6dU3RVyFf2gK3C65ogmkTcToIRweV+GmOiLlZZseAePg2ne9fBgsytVO22Hz98jq0RQJAGOyOX0eR7RAhdYTtI9izMwDQNXjUdMke4ii946QoNfgV8vW7D/nHMpzffWNolfhzYoMnMO+QeWwIwiATGBY83wJBAN5aLGltsuW/6ILvMTgciwdQlO3dTjzFXhbXURsyKWYF6QJnaeqNMVqr9F0lWXKQyuIu+mj5JRkZszOvS0jczNA=\n";
  356. // String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2xqT5IpFX+da8xx6+awprSI6bKvT5HndFucbuSoX+H0BJe7rWGoXwt3ZXhWgsi5A9/UNT6cjq0Ju2ED0sZB0ewZ/8PwgPdxGDSlxtXAGYw6/2SQHesVGNiuBpK4lgfb+Dngk04ZCsH4WqySVtu9DaOVU+PZLSgBypLoP1pwlDewIDAQAB";
  357. // String msg = "{\"dev_id\":\"868989034328943\",\"user_id\":\"\",\"team_id\":\"\"}";
  358. // String msg = DateTimeUtil.getDate();
  359. // String sm = RSAUtils.encrypt(msg,publicKey);
  360. // System.out.println(sm);
  361. // String str = "IGCJsmAEdYDle/WYAS4qd3hCp9TAZRz806Sm8p4KepJ2XuDaOnKh65homSgZJ88/pQs5rEm6Qmsg+8JqMgqjixD8jZhnToVriKzkhHe2UarS+sT8oUAvXrO98FwZv7GELgt4mCuitwxrREYq4QUtwI6Fc+SGdF0BjeKNUSWHifg=";
  362. //
  363. //
  364. //
  365. // String s = RSAUtils.decrypt(sm,privateKey);
  366. // System.out.println(s);
  367. //生成密钥对(公钥和私钥)
  368. Map map = RSAUtils.genKeyPair();
  369. System.out.println("----------------公钥----------------");
  370. System.out.println(RSAUtils.getPublicKey(map));
  371. System.out.println("----------------私钥----------------");
  372. System.out.println(RSAUtils.getPrivateKey(map));
  373. // String str = RSAUtils.encrypk("我爱你中国",privateKey);
  374. // System.out.println(str);
  375. //
  376. // String res = RSAUtils.decrypk(str,publicKey);
  377. // System.out.println(res);
  378. // String content = "F2cgqkTN1DehdikNnKtt5t0p+HrOr4hwWrbf9MGmksWUQFwWzFPbwt1at7uXYp8mt88KiIHrD/yKe8pkdzuq2VMTyvObryIbVannBHtzrhjBd2FoRQpaorMWtFuv7MZY3WmuqhzdhXMXAx1pB/hmEToncvW3ZpEWp7N5ItO89H8=";
  379. // String priKey = "MIICYQIBAAKBgQCijkoTGTel/S62WwUpXswc8m7Fdp+SY2ZMZKV8FeRD5QUi1mGYUHqmab7w/xN2vdNWI9sR9MPL4aO2IA57HD4HTGmidyCDmxl8QCRMSNwXroIP6mWpI5lHjajPth25n2gwsnYoAvrSfTnTkUAmoKUtQSg3/AZ9Rg8mf8NFY5AjGwIDAQABAoGBAJqS8W9NyHPn2DaBQNxBD5jrE1hj34NFT+6OuinPa1sAeSzSbMV4qdh6r53dADYmdcLwn41okZLbAmDaBBscUUZo/pIUPdGgU+P7RqpMtfwbq/RoleFpU2GWBErJ7/qKe6cfcEyuBhAMG8vLqtfA70P75vFAMoKhzWsUGtD5gQVZAkUAuRjD839QS/u17Xv6jWbcw0vxTD2UN/hyObyQORlX+LjxC5RCuYwkkWoXIGe94hzsyCoURc6RhakRM/LyqhCliTQ+PvcCPQDg0xUHm2qr8jfo1PTYf6Ks8Id9FZiqxD1VJh2OoRMSTCPo8O4PskbzictNu2siFLt0r3EKjCDP1jP0H/0CRFSsO5d8OiNINmU5PdjJoVvFtdCGqvMfuEEpPWChc1jYYYxGem+e6GuM+J9eVcLGMJswhK2aXX+jY7c8AD5D9zXYrFDpAj0Ap5OOXEIyy4FavRhmfCz+wyrxwoFjbv2gvaQQaeyTu5K3PXy/5UE783Ek8Yad/yQ26W2Ps43pMyF1TiS9AkQrfddTmrz7OZEfFtgaHuSj/8gbkGEdr7H8YIML/wZ1ozDkfxwsnzgclJXir/Qb2VjGOSzfUrlj2SaxgqIGfKI7DyEWeg==";
  380. // System.out.println(priKey);
  381. //
  382. // String priKey2 = "MIICewIBADANBgkqhkiG9w0BAQEFAASCAmUwggJhAgEAAoGBAKKOShMZN6X9LrZbBSlezBzybsV2n5JjZkxkpXwV5EPlBSLWYZhQeqZpvvD/E3a901Yj2xH0w8vho7YgDnscPgdMaaJ3IIObGXxAJExI3Beugg/qZakjmUeNqM+2HbmfaDCydigC+tJ9OdORQCagpS1BKDf8Bn1GDyZ/w0VjkCMbAgMBAAECgYEAmpLxb03Ic+fYNoFA3EEPmOsTWGPfg0VP7o66Kc9rWwB5LNJsxXip2Hqvnd0ANiZ1wvCfjWiRktsCYNoEGxxRRmj+khQ90aBT4/tGqky1/Bur9GiV4WlTYZYESsnv+op7px9wTK4GEAwby8uq18DvQ/vm8UAygqHNaxQa0PmBBVkCRQC5GMPzf1BL+7Xte/qNZtzDS/FMPZQ3+HI5vJA5GVf4uPELlEK5jCSRahcgZ73iHOzIKhRFzpGFqREz8vKqEKWJND4+9wI9AODTFQebaqvyN+jU9Nh/oqzwh30VmKrEPVUmHY6hExJMI+jw7g+yRvOJy027ayIUu3SvcQqMIM/WM/Qf/QJEVKw7l3w6I0g2ZTk92MmhW8W10Iaq8x+4QSk9YKFzWNhhjEZ6b57oa4z4n15VwsYwmzCErZpdf6NjtzwAPkP3NdisUOkCPQCnk45cQjLLgVq9GGZ8LP7DKvHCgWNu/aC9pBBp7JO7krc9fL/lQTvzcSTxhp3/JDbpbY+zjekzIXVOJL0CRCt911OavPs5kR8W2Boe5KP/yBuQYR2vsfxggwv/BnWjMOR/HCyfOByUleKv9BvZWMY5LN9SuWPZJrGCogZ8ojsPIRZ6";
  383. //
  384. // System.out.println(RSAUtils.decrypt(content,priKey2));
  385. }
  386. }

新增证书颁发模块

证书模块功能效果image.png

新增证书image.png
image.png

核心代码解析

  1. /**
  2. * @title exportCert
  3. * @description 导出证书
  4. * @author Administrator
  5. * @updateTime 2021/8/11 15:46
  6. */
  7. @RequiresPermissions("authCert:exportCert")
  8. @RequestMapping(value = "/exportCert", method = RequestMethod.GET)
  9. public void exportCert(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception {
  10. PageData pd = new PageData(request);
  11. PageData p = authCertService.findInfo(pd);
  12. String content = p.get("title")+"#"+p.get("start_time")+"#"+p.get("end_time")+"#"+p.get("ip_address")+"#"+p.get("mac_address")+"#"+p.get("other_limit");
  13. System.out.println(content);
  14. System.out.println(commonConfig.getPublicKey());
  15. String license = RSAUtils.encrypt(content,commonConfig.getPublicKey());
  16. // 创建根节点
  17. Element root = new Element("QingFeng");
  18. // 为根节点设置属性
  19. root.setAttribute("version", p.get("version_num").toString());
  20. // 创建Document对象,并为其设置根节点
  21. Document document = new Document(root);
  22. Element title = new Element("Title");
  23. Element sdate = new Element("SDate");
  24. Element edate = new Element("EDate");
  25. Element ip = new Element("IP");
  26. Element mac = new Element("MAC");
  27. Element olimit = new Element("OLimit");
  28. Element licenseKeys = new Element("LicenseKeys");
  29. title.addContent(p.get("title").toString());
  30. sdate.addContent(p.get("start_time").toString());
  31. edate.addContent(p.get("end_time").toString());
  32. ip.addContent(p.get("ip_address")+"");
  33. mac.addContent(p.get("mac_address")+"");
  34. olimit.addContent(p.get("other_limit")+"");
  35. licenseKeys.addContent(license);
  36. root.addContent(title);
  37. root.addContent(sdate);
  38. root.addContent(edate);
  39. root.addContent(ip);
  40. root.addContent(mac);
  41. root.addContent(olimit);
  42. root.addContent(licenseKeys);
  43. // 创建XMLOutputter对象
  44. XMLOutputter outputter = new XMLOutputter();
  45. //创建Format对象(自动缩进、换行)
  46. Format format = Format.getPrettyFormat();
  47. //为XMLOutputter设置Format对象
  48. outputter.setFormat(format);
  49. // 将Document转换成XML
  50. String tempPath = "D:\\home\\template\\QingfengLicense.xml";
  51. // String tempPath = session.getServletContext().getRealPath("/") + "/template/QingfengLicense.xml";
  52. outputter.output(document, new FileOutputStream(new File(tempPath)));
  53. // String toPath = session.getServletContext().getRealPath("/") + "/template/QingfengLicense.zip";
  54. /** 测试压缩方法2 */
  55. // List<File> fileList = new ArrayList<>();
  56. // fileList.add(new File(tempPath));
  57. // FileOutputStream fos2 = new FileOutputStream(new File(toPath));
  58. // ZipUtils.toZip(fileList, fos2);
  59. FileUtil.downFile(response, tempPath, "QingfengLicense.xml");
  60. // File file = new File(toPath);
  61. // file.delete();
  62. // file.deleteOnExit();
  63. }

证书下载

  1. public static void downFile(HttpServletResponse response, String filePath,
  2. String filename) throws Exception {
  3. File tempFile = new File(filePath);
  4. System.out.println(tempFile.exists());
  5. if (tempFile.exists()) {
  6. response.reset();
  7. response.setContentType("bin");//
  8. filename = new String(filename.getBytes(), "ISO-8859-1");
  9. response.addHeader("Content-Disposition", "attachment;filename="
  10. + filename);
  11. FileInputStream fis = new FileInputStream(tempFile);
  12. OutputStream os = response.getOutputStream();
  13. byte[] bb = new byte[1024*8];
  14. int i = 0;
  15. while ((i = fis.read(bb)) > 0) {
  16. os.write(bb, 0, i);
  17. }
  18. os.close();
  19. os.flush();
  20. fis.close();
  21. }
  22. }

证书认证校验-项目启动

证书校验源码

  1. /**
  2. * 继承Application接口后项目启动时会按照执行顺序执行run方法
  3. * 通过设置Order的value来指定执行的顺序
  4. */
  5. @Component
  6. @Order(value = 1)
  7. public class StartService implements ApplicationRunner {
  8. @Autowired
  9. private CommonConfig commonConfig;
  10. @Override
  11. public void run(ApplicationArguments applicationArguments) throws Exception {
  12. ClassPathResource classPathResource = new ClassPathResource("config/QingfengLicense.xml");
  13. // InputStream inputStream =classPathResource.getInputStream();
  14. // 创建SAXReader对象
  15. SAXReader reader = new SAXReader();
  16. // 加载xml文件
  17. Document dc= reader.read(classPathResource.getFile());
  18. // 获取根节点
  19. Element e = dc.getRootElement();
  20. System.out.println(e);
  21. // 获取迭代器
  22. Iterator it = e.elementIterator();
  23. // 遍历迭代器,获取根节点信息
  24. PageData pd = new PageData();
  25. while(it.hasNext()){
  26. Element book = (Element) it.next();
  27. pd.put(book.getName(),book.getStringValue());
  28. }
  29. String LicenseKeys = pd.get("LicenseKeys").toString();
  30. String license = RSAUtils.decrypt(LicenseKeys,commonConfig.getPrivateKey());
  31. if(Verify.verifyIsNull(license)){
  32. throw new RuntimeException("正式认证失败。");
  33. }else{
  34. System.out.println(license);
  35. String[] params = license.split("#", -1);
  36. System.out.println(params.length);
  37. String title = params[0];
  38. String start_time = params[1];
  39. String end_time = params[2];
  40. String ip_address = params[3];
  41. String mac_address = params[4];
  42. String other_limit = params[5];
  43. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  44. Date startDate = sdf.parse(start_time);
  45. Date endDate = sdf.parse(end_time);
  46. if(startDate.after(DateTimeUtil.getDDate()) || endDate.before(DateTimeUtil.getDDate())){
  47. throw new RuntimeException("认证正式已失效,请重新授权。");
  48. }
  49. System.out.println(IpUtils.getIpAddress());
  50. System.out.println(IpUtils.getMacAddress());
  51. if(Verify.verifyIsNotNull(ip_address)){
  52. if(!ip_address.equals(IpUtils.getIpAddress())){
  53. throw new RuntimeException("授权IP无效,请重新授权。");
  54. }
  55. }
  56. if(Verify.verifyIsNotNull(mac_address)){
  57. if(!mac_address.equals(IpUtils.getMacAddress())){
  58. throw new RuntimeException("授权MAC无效,请重新授权。");
  59. }
  60. }
  61. }
  62. // System.out.println("###############################项目启动成功###############################");
  63. }
  64. }

ApplicationRunner接口功能说明

1、ApplicationRunner
是一个接口,常用于项目启动后,(也就是ApringApplication.run()执行结束),立马执行某些逻辑。
可用于项目的准备工作,比如加载配置文件,加载执行流,定时任务等等。
2、如何使用ApplicationRunner
可以有多个实例实现该接口,但是一般需要增加注解@Order来指定加载顺序
@Order(-1)优先于@Order(0)
@Order(1)优先于@Order(2)

  1. @Component
  2. @Order(2)
  3. public class QingfengRunner implements ApplicationRunner {
  4. @Override
  5. public void run(ApplicationArguments args) throws Exception {
  6. System.out.println(args);
  7. System.out.println("这个是测试ApplicationRunner接口");
  8. }
  9. }

3、还有个接口,也可以实现和ApplicationRunner一样的功能
CommandLineRunner
CommandLineRunner接口的run方法接收的参数为String数组

  1. @Component
  2. @Order(value = 2)
  3. public class MyStartupRunner1 implements CommandLineRunner{
  4. @Override
  5. public void run(String... strings) throws Exception {
  6. System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 MyStartupRunner1 order 2 <<<<<<<<<<<<<");
  7. }
  8. }
  9. @Component
  10. @Order(value = 1)
  11. public class MyStartupRunner2 implements CommandLineRunner {
  12. @Override
  13. public void run(String... strings) throws Exception {
  14. System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 MyStartupRunner2 order 1 <<<<<<<<<<<<<");
  15. }
  16. }

证书认证校验-过滤器

过滤器证书校验源码

  1. @Configuration
  2. public class WebFilterConfig {
  3. @Bean
  4. public FilterRegistrationBean filterDemo3Registration() {
  5. FilterRegistrationBean registration = new FilterRegistrationBean();
  6. registration.setFilter(WebUrlFilter());
  7. registration.addUrlPatterns("/*");
  8. registration.addInitParameter("paramName", "paramValue");
  9. registration.setName("filterDemo3");
  10. registration.setOrder(1);
  11. return registration;
  12. }
  13. @Bean
  14. public Filter WebUrlFilter() {
  15. return new WebUrlFilter();
  16. }
  17. }
  1. public class WebUrlFilter implements Filter {
  2. private final Logger log = LoggerFactory.getLogger(getClass());
  3. @Autowired
  4. private CommonConfig commonConfig;
  5. @Override
  6. public void init(FilterConfig arg0) throws ServletException {
  7. System.out.println("doFilter 1");
  8. }
  9. @SneakyThrows
  10. @Override
  11. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
  12. // request.setAttribute("menuAuthParams","add,edit,del");
  13. System.out.println("doFilter 2");
  14. ClassPathResource classPathResource = new ClassPathResource("config/QingfengLicense.xml");
  15. // InputStream inputStream =classPathResource.getInputStream();
  16. // 创建SAXReader对象
  17. SAXReader reader = new SAXReader();
  18. // 加载xml文件
  19. Document dc= reader.read(classPathResource.getFile());
  20. // 获取根节点
  21. Element e = dc.getRootElement();
  22. System.out.println(e);
  23. // 获取迭代器
  24. Iterator it = e.elementIterator();
  25. // 遍历迭代器,获取根节点信息
  26. PageData pd = new PageData();
  27. while(it.hasNext()){
  28. Element book = (Element) it.next();
  29. pd.put(book.getName(),book.getStringValue());
  30. }
  31. String LicenseKeys = pd.get("LicenseKeys").toString();
  32. String license = RSAUtils.decrypt(LicenseKeys,commonConfig.getPrivateKey());
  33. if(Verify.verifyIsNull(license)){
  34. throw new RuntimeException("正式认证失败。");
  35. }else{
  36. System.out.println(license);
  37. String[] params = license.split("#", -1);
  38. System.out.println(params.length);
  39. String title = params[0];
  40. String start_time = params[1];
  41. String end_time = params[2];
  42. String ip_address = params[3];
  43. String mac_address = params[4];
  44. String other_limit = params[5];
  45. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  46. Date startDate = sdf.parse(start_time);
  47. Date endDate = sdf.parse(end_time);
  48. if(startDate.after(DateTimeUtil.getDDate()) || endDate.before(DateTimeUtil.getDDate())){
  49. throw new RuntimeException("认证正式已失效,请重新授权。");
  50. }
  51. System.out.println(IpUtils.getIpAddress());
  52. System.out.println(IpUtils.getMacAddress());
  53. if(Verify.verifyIsNotNull(ip_address)){
  54. if(!ip_address.equals(IpUtils.getIpAddress())){
  55. throw new RuntimeException("授权IP无效,请重新授权。");
  56. }
  57. }
  58. if(Verify.verifyIsNotNull(mac_address)){
  59. if(!mac_address.equals(IpUtils.getMacAddress())){
  60. throw new RuntimeException("授权MAC无效,请重新授权。");
  61. }
  62. }
  63. }
  64. filterChain.doFilter(request, response);
  65. }
  66. @Override
  67. public void destroy() {
  68. System.out.println("doFilter 3");
  69. }
  70. }

什么是过滤器

过滤器的概念

在讲Spring boot之前,我们先了解一下过滤器和拦截器。这两者在功能方面很类似,但是在具体技术实现方面,差距还是比较大的。在分析两者的区别之前,我们先理解一下AOP的概念,AOP不是一种具体的技术,而是一种编程思想。在面向对象编程的过程中,我们很容易通过继承、多态来解决纵向扩展。 但是对于横向的功能,比如,在所有的service方法中开启事务,或者统一记录日志等功能,面向对象的是无法解决的。所以AOP——面向切面编程其实是面向对象编程思想的一个补充。而我们今天讲的过滤器和拦截器都属于面向切面编程的具体实现。而两者的主要区别包括以下几个方面:
1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
2、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
3、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

过滤器的使用场景

Java过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器可以对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改;也可以对响应进行过滤,拦截或修改响应。
image.png
如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在web.xml中配置的顺序有关,配置在前的则位于链的前端。当请求通过了链中所有过滤器后就可以访问资源文件了,如果不能通过,则可能在中间某个过滤器中被处理掉。

在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。过滤链代码的执行顺序如下:
image.png
过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作,便于代码重用,不必每个servlet中还要进行相应的操作。

代码混淆ProGuard

ProGuard简介

ProGuard是一个混淆代码的开源项目,它的主要作用是混淆代码,ProGuard还包括以下4个功能。

  1. 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
  2. 优化(Optimize):对字节码进行优化,移除无用的指令。
  3. 混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
  4. 预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。

Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。

Springboot整合ProGuard代码混淆

  1. <!-- proguard混淆插件-->
  2. <plugin>
  3. <groupId>com.github.wvengen</groupId>
  4. <artifactId>proguard-maven-plugin</artifactId>
  5. <version>2.2.0</version>
  6. <executions>
  7. <execution>
  8. <!-- 打包的时候开始混淆-->
  9. <phase>package</phase>
  10. <goals>
  11. <goal>proguard</goal>
  12. </goals>
  13. </execution>
  14. </executions>
  15. <configuration>
  16. <injar>${project.build.finalName}.jar</injar>
  17. <!--输出的jar-->
  18. <outjar>${project.build.finalName}.jar</outjar>
  19. <!-- 是否混淆-->
  20. <obfuscate>true</obfuscate>
  21. <options>
  22. <option>-target 1.8</option> <!--指定java版本号-->
  23. <option>-dontshrink</option> <!--默认开启,不做收缩(删除注释、未被引用代码)-->
  24. <option>-dontoptimize</option><!--默认是开启的,这里关闭字节码级别的优化-->
  25. <!-- 不路过非公用类文件及成员-->
  26. <option>-dontskipnonpubliclibraryclasses</option>
  27. <option>-dontskipnonpubliclibraryclassmembers</option>
  28. <!--不用大小写混合类名机制-->
  29. <option>-dontusemixedcaseclassnames</option>
  30. <!-- 优化时允许访问并修改有修饰符的类和类的成员 -->
  31. <option>-allowaccessmodification</option>
  32. <!-- 确定统一的混淆类的成员名称来增加混淆-->
  33. <option>-useuniqueclassmembernames</option>
  34. <!-- 不混淆所有包名-->
  35. <!--<option>-keeppackagenames</option>-->
  36. <option>-adaptclassstrings</option><!--混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-->
  37. <option>-ignorewarnings
  38. </option><!-- 忽略warn消息,如果提示org.apache.http.* 这个包里的类有问题,那么就加入下述代码:-keep class org.apache.http.** { *; } -dontwarn org.apache.http.**-->
  39. <option>-keep class org.apache.logging.log4j.util.* { *; }</option>
  40. <option>-dontwarn org.apache.logging.log4j.util.**</option>
  41. <option>-keepattributes
  42. Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
  43. </option><!--对异常、注解信息在runtime予以保留,不然影响springboot启动-->
  44. <!--不混淆所有interface接口-->
  45. <!-- <option>-keepnames interface **</option>-->
  46. <option>-keepclassmembers enum * { *; }</option><!--保留枚举成员及方法-->
  47. <option>-keepparameternames</option>
  48. <option>-keepclasseswithmembers public class * {
  49. public static void main(java.lang.String[]);}
  50. </option> <!--保留main方法的类及其方法名-->
  51. <!--忽略note消息,如果提示javax.annotation有问题,那麽就加入以下代码-->
  52. <option>-dontnote javax.annotation.**</option>
  53. <option>-dontnote sun.applet.**</option>
  54. <option>-dontnote sun.tools.jar.**</option>
  55. <option>-dontnote org.apache.commons.logging.**</option>
  56. <option>-dontnote javax.inject.**</option>
  57. <option>-dontnote org.aopalliance.intercept.**</option>
  58. <option>-dontnote org.aopalliance.aop.**</option>
  59. <option>-dontnote org.apache.logging.log4j.**</option>
  60. <!-- ##### 以下为需要根据项目情况修改 comment by 青锋 ##### -->
  61. <!--入口程序类不能混淆,混淆会导致springboot启动不了-->
  62. <option>-keep class com.qingfeng.Application</option>
  63. <option>-keep class com.qingfeng.framework.**.* {*;}</option>
  64. <option>-keep class com.qingfeng.quartz.**.* {*;}</option>
  65. <!-- <option>-keep class com.qingfeng.system.entity.* {*;}</option>-->
  66. <!-- <option>-keep class com.qingfeng.system.model.* {*;}</option>-->
  67. <!-- <option>-keep class com.qingfeng.system.controller.* {*;}</option>-->
  68. <!-- ##### 以上为需要根据项目情况修改 comment by qingfeng ##### -->
  69. <option>-keep interface * extends * { *; }</option>
  70. <!--不混淆所有类,保存原始定义的注释-->
  71. <option>-keepclassmembers class * {
  72. @org.springframework.beans.factory.annotation.Autowired *;
  73. @org.springframework.beans.factory.annotation.Value *;
  74. }
  75. </option>
  76. </options>
  77. <libs>
  78. <!-- 添加依赖 java8-->
  79. <lib>${java.home}/lib/rt.jar</lib>
  80. <lib>${java.home}/lib/jce.jar</lib>
  81. </libs>
  82. </configuration>
  83. <dependencies>
  84. <!-- https://mvnrepository.com/artifact/net.sf.proguard/proguard-base -->
  85. <dependency>
  86. <groupId>net.sf.proguard</groupId>
  87. <artifactId>proguard-base</artifactId>
  88. <version>6.1.1</version>
  89. </dependency>
  90. </dependencies>
  91. </plugin>
  92. <!--Springboot repackage 打包-->
  93. <plugin>
  94. <groupId>org.springframework.boot</groupId>
  95. <artifactId>spring-boot-maven-plugin</artifactId>
  96. <executions>
  97. <execution>
  98. <!-- spingboot 打包需要repackage否则不是可执行jar -->
  99. <goals>
  100. <goal>repackage</goal>
  101. </goals>
  102. <configuration>
  103. <mainClass>com.qingfeng.Application</mainClass>
  104. </configuration>
  105. </execution>
  106. </executions>
  107. </plugin>

问题:
1.由于在混淆后代码a.class,b.class,c.class等方式,会导致springboot注入bean的时候报bean冲突,需要对生成bean策略进行修改,添加类:

  1. package com.qingfeng;
  2. import org.springframework.beans.factory.config.BeanDefinition;
  3. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  4. import org.springframework.context.annotation.AnnotationBeanNameGenerator;
  5. /**
  6. * @author Administrator
  7. * @version 1.0.0
  8. * @ProjectName qingfengThymeleaf
  9. * @Description sping下代码混淆后,不同包下的bean名相同会报bean冲突
  10. * @createTime 2022年04月25日 22:59:00
  11. */
  12. public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
  13. @Override
  14. public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
  15. //全限定类名
  16. String beanName = definition.getBeanClassName();
  17. return beanName;
  18. }
  19. }
  1. @ComponentScan(nameGenerator = UniqueNameGenerator.class)

image.png

执行maven打包

image.png
image.png

jd-gui查看编译后的class文件

打包之后,查看打包后的代码
image.png

项目运行

image.png
image.png