您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java中数据传输加密与签名那些事

时间:2020-03-08 13:25:04  来源:  作者:

引言

 

在日常开发中我们对HTTP数据传输并不陌生,前端需要与后端进行数据交互往往需要调用后端某个API,然而在前端与后端的请求过程中数据真的安全吗?下面了解一件关于0.01购买phone事件;

事件

在之前报导一个新闻,某个电商App应用因为程序Bug,被灰色产业0.01元撸走千台iphone手机,损失近千万,后来了解到也并非高手所为,其实就是该APP在购买接口中对数据传输缺少安全防范,通过HTTP抓包工具对传输中的数据进行修改,将原本几千元的iPhone手机价格修改为0.01进行购买;


案例

看完以上事件部分人可能还是不太抓包的概念,所谓抓包就是依赖某个工具,对客户端与服务端进行请求、或者对服务端对客户端响应时过程中进行拦截,可以将其请求数据或响应数据进行篡改;

为了能更好了解,这里我准备了一个模拟购买接口:

Java中数据传输加密与签名那些事

 

这里模拟前端正常调用购买接口;

http://localhost:8080/test?money=6000

按理后端应该会输出6000,这里我利用fiddle抓包工具对该请求进行拦截,将money参数改为1;

Java中数据传输加密与签名那些事

 


Java中数据传输加密与签名那些事

 

成功将合法价格改为非法价格,如果后端接口验证不注意即会按照该金额生成订单,则支付1元即可买到高额商品;

 

原由

导致以上事件发生无非就两种情况:

1.对敏感数据使用明文传输;

2.在前端进行校验后,后端接口并未对数据第二次校验;

 

如何解决?

在数据传输过程中对敏感数据进行加密,即使请求被截获,也只能获取到密文信息,无法篡改具体需要修改的数据;

传输过程中使用的加密方式通常分为两类:对称加密/非对称加密;

对称加密:市场上用的比较多的对称加密有DES、AES、3DES等,对称加密的流程是通过生成一把秘钥,分别存储客户端以及服务端各自一份,客户端在请求服务端前先将传输的数据通过该秘钥进行加密,服务端接收到请求之后,先将数据用该秘钥进行解密再进行处理,从而避免数据在传输中被篡改;

Java中数据传输加密与签名那些事

 

缺点:该秘钥在客户端加密与服务端解密使用的是同一把,而存储在客户端的秘钥并无法避免泄漏的风险,通过反编译等手段获取到秘钥,那么传输过程中拦截请求则照样可以解密密文数据进行篡改;

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加密的流程与对称加密不同,相对于更加安全,通过生成两把秘钥(私钥/公钥),公钥通常用来对传输过程中的数据进行加密,私钥则用来对加密数据进行解密,公钥加密的内容只能对应的私钥才能解密,私钥加密的内容只能由对应的公钥解密,通常客户端存储公钥、服务端则存储私钥,这样,即使公钥被泄漏也没法破解密文信息;

Java中数据传输加密与签名那些事

 

 

缺点:相对对称加密性能方面要慢很多,非敏感信息不建议使用;

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资料社区",更多干货等你学习;



Tags:Java   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  Tags: Java  点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  Tags: Java  点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Tags: Java  点击:(5)  评论:(0)  加入收藏
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Tags: Java  点击:(6)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  Tags: Java  点击:(11)  评论:(0)  加入收藏
传统游戏项目一般使用TCP协议进行通信,得益于它的稳定和可靠,不过在网络不稳定的情况下,会出现丢包严重。不过近期有不少基于UDP的应用层协议,声称对UDP的不可靠进行了改造,这意...【详细内容】
2021-12-23  Tags: Java  点击:(12)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  Tags: Java  点击:(12)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  Tags: Java  点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  Tags: Java  点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  Tags: Java  点击:(19)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(12)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(22)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条