前言:
如今小伙伴们对“签名java”大概比较着重,看官们都想要了解一些“签名java”的相关知识。那么小编也在网络上网罗了一些关于“签名java””的相关知识,希望姐妹们能喜欢,姐妹们一起来了解一下吧!引言
在日常开发中我们对HTTP数据传输并不陌生,前端需要与后端进行数据交互往往需要调用后端某个API,然而在前端与后端的请求过程中数据真的安全吗?下面了解一件关于0.01购买phone事件;
事件
在之前报导一个新闻,某个电商APP应用因为程序Bug,被灰色产业0.01元撸走千台iPhone手机,损失近千万,后来了解到也并非高手所为,其实就是该APP在购买接口中对数据传输缺少安全防范,通过HTTP抓包工具对传输中的数据进行修改,将原本几千元的iPhone手机价格修改为0.01进行购买;
案例
看完以上事件部分人可能还是不太抓包的概念,所谓抓包就是依赖某个工具,对客户端与服务端进行请求、或者对服务端对客户端响应时过程中进行拦截,可以将其请求数据或响应数据进行篡改;
为了能更好了解,这里我准备了一个模拟购买接口:
这里模拟前端正常调用购买接口;
按理后端应该会输出6000,这里我利用fiddle抓包工具对该请求进行拦截,将money参数改为1;
成功将合法价格改为非法价格,如果后端接口验证不注意即会按照该金额生成订单,则支付1元即可买到高额商品;
原由
导致以上事件发生无非就两种情况:
1.对敏感数据使用明文传输;
2.在前端进行校验后,后端接口并未对数据第二次校验;
如何解决?
在数据传输过程中对敏感数据进行加密,即使请求被截获,也只能获取到密文信息,无法篡改具体需要修改的数据;
传输过程中使用的加密方式通常分为两类:对称加密/非对称加密;
对称加密:市场上用的比较多的对称加密有DES、AES、3DES等,对称加密的流程是通过生成一把秘钥,分别存储客户端以及服务端各自一份,客户端在请求服务端前先将传输的数据通过该秘钥进行加密,服务端接收到请求之后,先将数据用该秘钥进行解密再进行处理,从而避免数据在传输中被篡改;
缺点:该秘钥在客户端加密与服务端解密使用的是同一把,而存储在客户端的秘钥并无法避免泄漏的风险,通过反编译等手段获取到秘钥,那么传输过程中拦截请求则照样可以解密密文数据进行篡改;
Des加密案例:
public class DesDecrypt { //加密字段 private static String src = "Java资料社区"; public static void main(String[] args) { jdkDES(); bcDES(); } /*JDK实现*/ public static void jdkDES() { try { //生成KEY KeyGenerator keyGenerator = KeyGenerator.getInstance("DES"); keyGenerator.init(56); SecretKey secretKey = keyGenerator.generateKey(); byte[] bytesKey = secretKey.getEncoded(); //KEY转换 DESKeySpec desKeySpec = new DESKeySpec(bytesKey); SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); Key convertSecretKey = factory.generateSecret(desKeySpec); //加密 Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, convertSecretKey); byte[] result = cipher.doFinal(src.getBytes()); System.out.println("jdk des encrypt : " + new String(Hex.encode(result))); //解密 cipher.init(Cipher.DECRYPT_MODE, convertSecretKey); result = cipher.doFinal(result); System.out.println("jdk des decrypt : " + new String(result).toString()); } catch (Exception e) { e.printStackTrace(); } } /*BC实现*/ public static void bcDES() { try { Security.addProvider(new BouncyCastleProvider()); //生成KEY KeyGenerator keyGenerator = KeyGenerator.getInstance("DES", "BC"); keyGenerator.getProvider(); keyGenerator.init(56); SecretKey secretKey = keyGenerator.generateKey(); byte[] bytesKey = secretKey.getEncoded(); //KEY转换 DESKeySpec desKeySpec = new DESKeySpec(bytesKey); SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); Key convertSecretKey = factory.generateSecret(desKeySpec); //加密 Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, convertSecretKey); byte[] result = cipher.doFinal(src.getBytes()); System.out.println("bc des encrypt : " + new String(Hex.encode(result))); //解密 cipher.init(Cipher.DECRYPT_MODE, convertSecretKey); result = cipher.doFinal(result); System.out.println("bc des decrypt : " + new String(result).toString()); } catch (Exception e) { e.printStackTrace(); } }}//运行结果//jdk des encrypt : ab6afc9a81584573cf2f50ceccd28628bd10885ebd84f37c//jdk des decrypt : Java资料社区//bc des encrypt : 34fd3ed3d0b6d5ab7baf6db300420f3c4094a2061bb2dd34//bc des decrypt : Java资料社区
非对称加密:市场上用的比较多的通常为RSA加密,RSA加密的流程与对称加密不同,相对于更加安全,通过生成两把秘钥(私钥/公钥),公钥通常用来对传输过程中的数据进行加密,私钥则用来对加密数据进行解密,公钥加密的内容只能对应的私钥才能解密,私钥加密的内容只能由对应的公钥解密,通常客户端存储公钥、服务端则存储私钥,这样,即使公钥被泄漏也没法破解密文信息;
缺点:相对对称加密性能方面要慢很多,非敏感信息不建议使用;
RSA案例:
public class RSAUtils { protected static final Log log = LogFactory.getLog(RSAUtils.class); private static String KEY_RSA_TYPE = "RSA"; private static int KEY_SIZE = 1024;//JDK方式RSA加密最大只有1024位 private static int ENCODE_PART_SIZE = KEY_SIZE/8; public static final String PUBLIC_KEY_NAME = "public"; public static final String PRIVATE_KEY_NAME = "private"; public static void main(String[] args) { Map<String,String> map=RSAUtils.createRSAKeys(); //创建公私钥 String str="Java资料社区"; String enStr=encode(str,map.get("public"));//公钥加密 System.out.println("加密后:"+enStr); String deStr=decode(enStr,map.get("private")); System.out.println("解密后:"+deStr); } /** * 创建公钥秘钥 * @return */ public static Map<String,String> createRSAKeys(){ Map<String,String> keyPairMap = new HashMap<>();//里面存放公私秘钥的Base64位加密 try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE); keyPairGenerator.initialize(KEY_SIZE,new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); //获取公钥秘钥 String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded()); String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded()); //存入公钥秘钥,以便以后获取 keyPairMap.put(PUBLIC_KEY_NAME,publicKeyValue); keyPairMap.put(PRIVATE_KEY_NAME,privateKeyValue); } catch (NoSuchAlgorithmException e) { log.error("当前JDK版本没找到RSA加密算法!"); e.printStackTrace(); } return keyPairMap; } /** * 公钥加密 * 描述: * 1字节 = 8位; * 最大加密长度如 1024位私钥时,最大加密长度为 128-11 = 117字节,不管多长数据,加密出来都是 128 字节长度。 * @param sourceStr * @param publicKeyBase64Str * @return */ public static String encode(String sourceStr,String publicKeyBase64Str){ byte [] publicBytes = Base64.decodeBase64(publicKeyBase64Str); //公钥加密 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes); List<byte[]> alreadyEncodeListData = new LinkedList<>(); int maxEncodeSize = ENCODE_PART_SIZE - 11; String encodeBase64Result = null; try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE); cipher.init(Cipher.ENCRYPT_MODE,publicKey); byte[] sourceBytes = sourceStr.getBytes("utf-8"); int sourceLen = sourceBytes.length; for(int i=0;i<sourceLen;i+=maxEncodeSize){ int curPosition = sourceLen - i; int tempLen = curPosition; if(curPosition > maxEncodeSize){ tempLen = maxEncodeSize; } byte[] tempBytes = new byte[tempLen];//待加密分段数据 System.arraycopy(sourceBytes,i,tempBytes,0,tempLen); byte[] tempAlreadyEncodeData = cipher.doFinal(tempBytes); alreadyEncodeListData.add(tempAlreadyEncodeData); } int partLen = alreadyEncodeListData.size();//加密次数 int allEncodeLen = partLen * ENCODE_PART_SIZE; byte[] encodeData = new byte[allEncodeLen];//存放所有RSA分段加密数据 for (int i = 0; i < partLen; i++) { byte[] tempByteList = alreadyEncodeListData.get(i); System.arraycopy(tempByteList,0,encodeData,i*ENCODE_PART_SIZE,ENCODE_PART_SIZE); } encodeBase64Result = Base64.encodeBase64String(encodeData); } catch (Exception e) { e.printStackTrace(); } return encodeBase64Result; } /** * 私钥解密 * @param sourceBase64RSA * @param privateKeyBase64Str */ public static String decode(String sourceBase64RSA,String privateKeyBase64Str){ byte[] privateBytes = Base64.decodeBase64(privateKeyBase64Str); byte[] encodeSource = Base64.decodeBase64(sourceBase64RSA); int encodePartLen = encodeSource.length/ENCODE_PART_SIZE; List<byte[]> decodeListData = new LinkedList<>();//所有解密数据 String decodeStrResult = null; //私钥解密 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes); try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE); cipher.init(Cipher.DECRYPT_MODE,privateKey); int allDecodeByteLen = 0;//初始化所有被解密数据长度 for (int i = 0; i < encodePartLen; i++) { byte[] tempEncodedData = new byte[ENCODE_PART_SIZE]; System.arraycopy(encodeSource,i*ENCODE_PART_SIZE,tempEncodedData,0,ENCODE_PART_SIZE); byte[] decodePartData = cipher.doFinal(tempEncodedData); decodeListData.add(decodePartData); allDecodeByteLen += decodePartData.length; } byte [] decodeResultBytes = new byte[allDecodeByteLen]; for (int i = 0,curPosition = 0; i < encodePartLen; i++) { byte[] tempSorceBytes = decodeListData.get(i); int tempSourceBytesLen = tempSorceBytes.length; System.arraycopy(tempSorceBytes,0,decodeResultBytes,curPosition,tempSourceBytesLen); curPosition += tempSourceBytesLen; } decodeStrResult = new String(decodeResultBytes,"UTF-8"); }catch (Exception e){ e.printStackTrace(); } return decodeStrResult; }}//运行结果//加密后:bapvkf7RsocUqpr7JJ5yMDQSrMAKnWVuHG2buJDhV0DGMTlAKezcy7Qyc8f5DVVHralZ5I0nSZdQOVaIG7ndP/bvtNcQVJaaigRSaQTfmf7xiNNuWQf71nQJmiUlHdzijUWB0HbilWBeZ71FZT9djoV1X6EZDB3oH1DQwwOmvow=//解密后:Java资料社区
经过以上加密之后,基本上能很大程度解决数据在传输当中数据被泄漏的几率,但是并没法保证绝对的安全,RSA加密中如果公钥被泄漏,攻击者照样有办法能够根据泄漏的公钥,重新模拟一份加密数据传输到服务端,而服务端并没法辨别中途是否被篡改过,那么签名技术就是用于验证、防止中途是否被篡改;
签名:通过使用两对RSA秘钥 (A[公钥、私钥],B[公钥、私钥]) ,通常客户端存储A公钥、B私钥,服务端则存储A私钥、B公钥,A用于加解密,B用于签名以及验签;
流程:客户端用A公钥加密过后,再通过B私钥将加密过后的数据进行签名,最后将加密数据与签名一起发送到服务端,服务端接收到数据之后,首先通过B公钥对该加密数据以及签名进行对比验签,如果验证不一致则代表中途被篡改过,否则正常;
RSA签名案例:
public class RSASignature { public static void main(String[] args) { /*创建两份公私钥,map1用于加解密、map2用于签名*/ Map<String,String> map1=RSAUtils.createRSAKeys(); Map<String,String> map2=RSAUtils.createRSAKeys(); String str="Java资料社区"; String enStr=RSAUtils.encode(str,map1.get("public"));//map1公钥加密 System.out.println("加密后:"+enStr); String sign=sign(enStr,map2.get("private"));//map2私钥签名 System.out.println("签名:"+sign); //enStr+="1"; //模拟中途被篡改,则验签false boolean flag=doCheck(enStr,sign,map2.get("public"));//map2公钥验签 System.out.println("验签结果:"+flag); } /** * 签名算法 */ public static final String SIGN_ALGORITHMS = "SHA1WithRSA"; /** * RSA签名 */ public static String sign(String content, String privateKey) { try { PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); KeyFactory keyf = KeyFactory.getInstance("RSA"); PrivateKey priKey = keyf.generatePrivate(priPKCS8); java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS); signature.initSign(priKey); signature.update(content.getBytes()); byte[] signed = signature.sign(); return Base64.encode(signed); } catch (Exception e) { e.printStackTrace(); } return null; } /** * RSA验签 */ public static boolean doCheck(String content, String sign, String publicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] encodedKey = Base64.decode(publicKey); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); java.security.Signature signature = java.security.Signature .getInstance(SIGN_ALGORITHMS); signature.initVerify(pubKey); signature.update(content.getBytes()); boolean bverify = signature.verify(Base64.decode(sign)); return bverify; } catch (Exception e) { e.printStackTrace(); } return false; }}//运行结果://加密后:Nv1kTCukFAziwsCYPZQDQ8WqII1v5DKlTaFlcgkXQACuO01rFuvPAu/PXlmVuHN0i2Xx5B4ZjsKIs2YJHUzIunuqkPchKZM29b52jv7TLPaeZUeQFmC5gKEpEHSTWfUK9T7YF20+kK6Ey0rWRgOBd3ZoVPjvCoNXAlhyEYkBx7Q=//签名:fAkREdyQsioXeDi/CLaBQDP+V0K6W8DGXTBYZsH5GS8O30+ZKCfyTwvGNIZ++XIekt0P6xo1T6L7BRdT/it5qNwcqudxoolgf8KkhkSRFCI6LjJ6TYxFfCnvtMv7dxXDkR30E0AR9jyqNCUVE6ljUDsSL7PvUFpqOBDTcd+l2uA=//验签结果:true
总结
1.对称加密效率性能高,由于秘钥存在泄漏的可能,并不能保证加密数据不被破解,建议对普通数据进行使用,对于敏感数据(金额、身份证等等)避免使用;
2.非对称加密效率性能低,分为公私钥两把,即使公钥泄漏,也能保证数据不被破解,建议对敏感数据进行使用;
3.通过非对称加密+签名能够很大程度防止传输过程中信息被篡改,但是并不是绝对的安全;
关注微信公众号"Java资料社区",更多干货等你学习;
如果大家觉得有用 请分享出去 让更多人看到!予人玫瑰手有余香
标签: #签名java