精华内容
下载资源
问答
  • Java MD5签名实现签名规则:变现猫与开发者之间所有的请求进行md5签名,确保传输的安全可靠。签名原理:md5签名的原理如下:将所有的参数值与appSecret按参数名升序进行排列。Md5(value1+value2+...appSecret...+...

    Java MD5签名实现

    签名规则:

    变现猫与开发者之间所有的请求进行md5签名,确保传输的安全可靠。

    签名原理:

    md5签名的原理如下:将所有的参数值与appSecret按参数名升序进行排列。

    Md5(value1+value2+...appSecret...+valueN)

    appSecret在签名中的顺序取决于他在所有参数名中的顺序。

    注意:签名验证时,必须遍历request请求中的所有参数,进行签名验证。变现猫向开发者发起的请求,未来有可能会添加业务参数,开发者在验证请求时,务必对所有参数进行遍历,全部加入签名验证数据中。

    如果开发者写死签名验证参数,未来变现猫升级参数将导致开发者服务不可用,请谨慎。

    签名规则测试用例:

    为了方便开发者对签名方法进行测试验证,下面提供几个测试用例,开发者可以参考下面的用例,使用相同的输入参数,看产生的签名与我们提供的签名是否一致。

    原参数列表:

    appKey=9f676b9b496f44ff9bb81ae6ed3e9360,

    appUid=1,

    sex=0,

    appType=app,

    mobile=13366668888,

    nickName=变现猫,

    appEntrance=1,

    appHomeUrl=http://www.bianxianmao.com,

    face=http://www.bianxianmao.com/web/images/logo-08.png,

    redirect=http://1.migree.com.cn/qmdb/shop/weixin/index.html#/tab/home,

    redirectLoginUrl=http://www.bianxianmao.com/login.html,

    timestamp=1480925973841

    按参数名升序排列后的参数列表(带appSecret):

    appEntrance=1,

    appHomeUrl=http://www.bianxianmao.com,

    appKey=9f676b9b496f44ff9bb81ae6ed3e9360,

    appSecret=fc2ce7ee45ae45729932b25d6d2607b9,

    appType=app,

    appUid=1,

    face=http://www.bianxianmao.com/web/images/logo-08.png,

    mobile=13366668888,

    nickName=变现猫,

    redirect=http://1.migree.com.cn/qmdb/shop/weixin/index.html#/tab/home,

    redirectLoginUrl=http://www.bianxianmao.com/login.html,

    sex=0,

    timestamp=1480925973841MD5签名appSecret秘钥:fc2ce7ee45ae45729932b25d6d2607b9

    待签名原串(带appSecret): 1http://www.bianxianmao.com9f676b9b496f44ff9bb81ae6ed3e9360fc2ce7ee45ae45729932b25d6d2607b9app1http://www.bianxianmao.com/web/images/logo-08.png13366668888变现猫http://1.migree.com.cn/qmdb/shop/weixin/index.html#/tab/homehttp://www.bianxianmao.com/login.html01480925973841MD5签名后的sign字符串为: a8695e677323a90199c82b0201caeacc

    签名后的URL:

    http://1.migree.com.cn/qmdb/shop/weixin/index.html#/tab/home?face=http%3A%2F%2Fwww.bianxianmao.com%2Fweb%2Fimages%2Flogo-08.png&sex=0&appEntrance=1&appHomeUrl=http%3A%2F%2Fwww.bianxianmao.com&sign=a8695e677323a90199c82b0201caeacc&timestamp=1480925973841&nickName=%E5%8F%98%E7%8E%B0%E7%8C%AB&redirect=http%3A%2F%2F1.migree.com.cn%2Fqmdb%2Fshop%2Fweixin%2Findex.html%23%2Ftab%2Fhome&appUid=1&redirectLoginUrl=http%3A%2F%2Fwww.bianxianmao.com%2Flogin.html&appType=app&appKey=9f676b9b496f44ff9bb81ae6ed3e9360&mobile=13366668888

    展开全文
  • MD5加密签名 为了数据的安全性、完整性,防止数据在传输过程被篡改。当post请求会加上md5签名来校验数据, 多一个sign标签,sign的值就是md5生成的字符串 ps: 项目上线加班,有点疲惫哈。趁某些同事再改bug,抽点...

    MD5加密签名

    为了数据的安全性、完整性,防止数据在传输过程被篡改。当post请求会加上md5签名来校验数据,

    多一个sign标签,sign的值就是md5生成的字符串

    ps: 项目上线加班,有点疲惫哈。趁某些同事再改bug,抽点时间写下吧。

    效果图

    在这里插入图片描述

    1. 下载md5.js文件

    md5下载

    注意:js可以直接使用, 如果在小程序里面,修改以下两点, 如下图

    说明:小程序中使用修改步骤

    1. 去掉最外层(function ($) {}包裹,其次 左图 备注判断去掉。
    2. 方法名为md5, 使用export导出。【见右图
      在这里插入图片描述

    2. MD5加密数据

    数据签名后再发送请求, 你看会带请求数据中多一个sign字段。

    • 定义key
    • 把需要签名的数据进行排序【排序的格式和后端定义好】
    • 拼接数据+key
    • 调用md5方法加密
    • 发送请求

    说明 项目中会把签名代码发送网络请求进行封装,通过参数传递。

    registerTap() {
        let that = this;
        let params = {
            mobile: this.mobile,
            loginPwd: this.password,
        }
    
        var appkey = 'VerificationSign2019'; //key是自定义
        var newObj = this.objKeySort(params);
        console.log('需要签名数据:', newObj); //{mobile: "15899999999", loginPwd: "123456"}
        let connects = '';
        for (let item in newObj) {
            connects += newObj[item];
        }
        
        // 拼接格式登录: 15899999999123456VerificationSign2019  进行加密
        connects += appkey;
        console.log('拼接格式后', connects); //15899999999123456VerificationSign2019
        
        // md5加密
        params.sign = md5(connects); //4576c0b7e32f078d8fb8297e305a9151
        
        //发送请求
        let url = `${this.url}/invitationRegister`
        that.$http.post(url, params, {
            emulateJSON: true
        }).then((res) => {
            console.log(res);
            let datas = res.body;
        })
    },
    

    3. 排序

    默认是升序

    //json数据排序
    objKeySort(obj, typesort = 'sort') { //排序的函数
        if (typesort == 'sort') {
            var newkey = Object.keys(obj).sort(); //升序
        } else {
            var newkey = Object.keys(obj).sort().reverse(); //降序
        }
        //先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组
        var newObj = {}; //创建一个新的对象,用于存放排好序的键值对
        for (var i = 0; i < newkey.length; i++) { //遍历newkey数组
            newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对
        }
        return newObj; //返回排好序的新对象
    }
    

    感谢大神文章

    展开全文
  • HMAC-MD5签名的Java实现

    2020-10-26 22:14:23
    中电联的协议《T/CEC 102.4—2016 电动汽车充换电服务信息交换 第4部分:数据传输及安全》中描述了一个名为HMAC-MD5签名方法,具体如下图: 用于数据传输过程的报文签名。本文是记录如何实现这个签名。 (顺便

    协议标准

    中电联的协议《T/CEC 102.4—2016 电动汽车充换电服务信息交换 第4部分:数据传输及安全》中描述了一个名为HMAC-MD5的签名方法,具体如下图:

    在这里插入图片描述
    用于数据传输过程的报文签名。本文是记录如何实现这个签名。

    (顺便吐槽一下这个协议中算法的文字描述部分非常不严谨,让人很疑惑。描述中总是用字符串代替字节数组,让我以为要把数据转为字符呢。结果根本不是。最后是看着公式实现的。)

    准备内容

    MD5工具类Md5Utils

    根据协议描述,我们需要Md5的实现。我们这里直接使用之前的工具类,不再这里讨论。

    这个工具类类图如下:
    Md5Utils类图
    代码:

    package net.wangds.utils;
    
    import java.io.UnsupportedEncodingException;
    import java.math.BigInteger;
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.security.Security;
    
    /**
     * md5工具.
     */
    public class Md5Utils {
    
        /**
         * 字符串的md5.
         * @param plainText 输入字符串.
         * @return md5(utf8编码).
         */
        public static String md5(String plainText) {
            return md5(plainText, StandardCharsets.UTF_8);
        }
    
        /**
         * 字符串的md5.
         * @param plainText 输入字符串.
         * @param encode 字符集.
         * @return md5.
         * @see #md5(String, Charset)
         */
        @Deprecated
        public static String md5(String plainText, String encode) {
            try {
                return md5(plainText.getBytes(encode));
            } catch (UnsupportedEncodingException e) {
                return "";
            }
        }
    
        /**
         * 字符串的md5.
         * @param plainText 输入字符串.
         * @param charset 字符集.
         * @return md5.
         */
        public static String md5(String plainText, Charset charset) {
            return md5(plainText.getBytes(charset));
        }
    
        /**
         * 字符串的md5.
         * @param plainText 输入字符串.
         * @return md5(utf8编码).
         */
        public static String md5_16(String plainText) {
            return md5_16(plainText, StandardCharsets.UTF_8);
        }
    
        /**
         * 字符串的md5.
         * @param plainText 输入字符串.
         * @param encode 字符集.
         * @return md5.
         * @see #md5(String, Charset)
         */
        @Deprecated
        public static String md5_16(String plainText, String encode) {
            try {
                return md5(plainText.getBytes(encode)).substring(8,24);
            } catch (UnsupportedEncodingException e) {
                return "";
            }
        }
    
        /**
         * 字符串的16位md5.
         * @param plainText 输入字符串.
         * @param charset 字符集.
         * @return md5.
         */
        public static String md5_16(String plainText, Charset charset){
            return md5(plainText, charset).substring(8,24);
        }
    
        /**
         * 数据的md5.
         * @param data 输入字符串.
         * @return md5.
         */
        public static byte[] md5Bytes(byte[] data) {
            try {
                // 生成一个MD5加密计算摘要
                MessageDigest md = MessageDigest.getInstance("MD5");
                //对字符串进行加密
                md.update(data);
                //获得加密后的数据
                return md.digest();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("没有md5这个算法");
            }
        }
    
        /**
         * 数据的md5.
         * @param data 输入字符串.
         * @return md5.
         */
        public static String md5(byte[] data) {
            return HexUtils.bytes2HexString(md5Bytes(data));
        }
    
    
    }
    
    

    Hex编码工具类HexUtils

    同时,测试等工作也需要一个Hex编码工具,类图如下:
    HexUtils的类图

    HexUtils的代码如下:

    package net.wangds.utils;
    
    @SuppressWarnings("unused")
    public class HexUtils {
    
        /**
         * bytes2HexString.
         * 字节数组转16进制字符串.
         * @param bs 字节数组.
         * @return 16进制字符串.
         */
        public static String bytes2HexString(byte[] bs) {
            StringBuilder result = new StringBuilder();
            for (byte b1 : bs) {
                result.append(String.format("%02X", b1));
            }
            return result.toString();
        }
    
        /**
         * 字节数组转大写16进制字符串.
         * @param bs 字节数组.
         * @return 16进制字符串.
         */
        public static String bytes2UpperCaseHexString(byte[] bs) {
            return bytes2HexString(bs).toUpperCase();
        }
    
        /**
         * 字节数组转小写16进制字符串.
         * @param bs 字节数组.
         * @return 16进制字符串.
         */
        public static String bytes2LowerCaseHexString(byte[] bs) {
            return bytes2HexString(bs).toLowerCase();
        }
    
    
        /**
         * bytes2HexString.
         * 字节数组转16进制字符串.
         * @param bs 字节数组.
         * @return 16进制字符串.
         */
        public static String bytes2HexString(byte[] bs, int offset, int length) {
            StringBuilder result = new StringBuilder();
            for (int i=0; i<length; i++) {
                byte b1 = bs[offset+i];
                result.append(String.format("%02X", b1));
            }
            return result.toString();
        }
    
        /**
         * hexString2Bytes.
         * 16进制字符串转字节数组.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static byte[] hexString2Bytes(String src) {
            int l = src.length() / 2;
            byte[] ret = new byte[l];
            for (int i = 0; i < l; i++) {
                ret[i] = Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
            }
            return ret;
        }
    
    
        /**
         * string2HexUTF8.
         * 字符UTF8串转16进制字符串.
         * @param strPart 字符串.
         * @return 16进制字符串.
         * 
         */
        public static String string2HexUTF8(String strPart) {
    
            return string2HexString(strPart,"UTF-8");
        }
    
        /**
         * string2HexUTF8.
         * 字符UTF-16LE串转16进制字符串,此UTF-16LE等同于C#中的Unicode
         * @param strPart 字符串.
         * @return 16进制字符串.
         * 
         */
        public static String string2HexUTF16LE(String strPart) {
    
            return string2HexString(strPart,"UTF-16LE");
        }
    
        /**
         * string2HexUnicode.
         * 字符Unicode串转16进制字符串.
         * @param strPart 字符串.
         * @return 16进制字符串.
         * 
         */
        public static String string2HexUnicode(String strPart) {
    
            return string2HexString(strPart,"Unicode");
        }
        /**
         * string2HexGBK.
         * 字符GBK串转16进制字符串.
         * @param strPart 字符串.
         * @return 16进制字符串.
         * 
         */
        public static String string2HexGBK(String strPart) {
    
            return string2HexString(strPart,"GBK");
        }
    
        /**
         * string2HexString.
         * 字符串转16进制字符串.
         * @param strPart 字符串.
         * @param tochartype hex目标编码.
         * @return 16进制字符串.
         * 
         */
        public static String string2HexString(String strPart,String tochartype) {
            try{
                return bytes2HexString(strPart.getBytes(tochartype));
            }catch (Exception e){
                return "";
            }
        }
    
        /**
         * hexUTF82String.
         * 16进制UTF-8字符串转字符串.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static String hexUTF82String(String src) {
    
            return hexString2String(src,"UTF-8","UTF-8");
        }
    
        /**
         * hexUTF16LE2String.
         * 16进制UTF-8字符串转字符串,,此UTF-16LE等同于C#中的Unicode.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static String hexUTF16LE2String(String src) {
    
            return hexString2String(src,"UTF-16LE","UTF-8");
        }
    
        /**
         * hexGBK2String.
         * 16进制GBK字符串转字符串.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static String hexGBK2String(String src) {
    
            return hexString2String(src,"GBK","UTF-8");
        }
    
        /**
         * hexUnicode2String.
         * 16进制Unicode字符串转字符串.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static String hexUnicode2String(String src) {
            return hexString2String(src,"Unicode","UTF-8");
        }
    
        /**
         * hexString2String 16进制字符串转字符串.
         * @param src 16进制字符串.
         * @return 字节数组.
         * 
         */
        public static String hexString2String(String src,String oldchartype, String chartype) {
            byte[] bts=hexString2Bytes(src);
            try{if(oldchartype.equals(chartype))
                return new String(bts,oldchartype);
            else
                return new String(new String(bts,oldchartype).getBytes(),chartype);
            }
            catch (Exception e){
    
                return"";
            }
        }
    
    
    }
    
    

    公共部分

    AbstractHMacMd5Utils类

    • 协议中的报文组装过程

    从协议应用场景上看,用于充电桩的信息交换,而且示例中,直接给了运营商Id,信息,时间戳,序列号等字段内容。

    这些字段如何使用并没有在描述HMAC-MD5的附录C中说明,而是在协议正文。这部分内容如下图:
    参数签名规范

    • 报文组装过程的实现

    我们在这里用AbstractHMacMd5Utils类封装这个组装报文的功能。类图如下:

    AbstractHMacMd5UtilsassembleData(opId:String, data:String, ts:String, seq:String,charset:Charset)

    这个类是后续开发的工具类的基类,在用工具类中可以方便地调用组装报文的方法assembleData

    类代码如下:

    package net.wangds.cnpg.cpmn.utils;
    
    import java.nio.charset.Charset;
    
    /**
     * .
     * <p></p>
     *
     * @author eastone 2020/10/21 11:45.
     */
    public class AbstractHMacMd5Utils {
    
        public static byte[] assembleData(String operatorID, String data, String timestamp, String seq, Charset charset){
            return String.format("%s%s%s%s", operatorID,data, timestamp, seq).getBytes(charset);
        }
    
    }
    
    

    第一次实现-简单的HMacMd5Utils类

    算法中的掩码

    根据算法定义,需要有两个用于异或操作的掩码opad和ipad。

    AbstractHMacMd5UtilsHMacMd5Utils<static> <final> - opad : byte = 0x5c;<static> <final> - ipad : byte = 0x36;<static> <final> - keyLen: byte = 64;

    同时定义了一个常量keyLen用来代表密钥长度。

    密钥处理

    算法中的密钥,长度为64字节,也就是64*8=512 bit。

    根据算法要求,密钥长度小于64位的时候,要补"\0"。协议描述给的示例密钥是"1234567890abcdef",所以最开始补了字符"0",结果不对,要用"\0"才可以。

    另外,当密钥字符串长度大于64字节的时候,我们这里选择用密钥的md5散列码作为真实密钥。

    关于补0的问题,我们这里没有使用补零的过程,因为Java中byte默认是0。密钥的处理过程用到了ByteBuffer,创建固定大小的ByteBuffer对象,从起始位置写入密钥,未写入的部分自然是"\0".

    Created with Raphaël 2.2.0开始初始化buf:ByteBuffer变量。参数密钥长度是否大于64字节?向buf中写入密钥的md5散列码结束:返回buf中的数组将密钥写入buf对象yesno

    实现后,类图如下:

    AbstractHMacMd5UtilsHMacMd5Utils<static> <final> - opad : byte = 0x5c;<static> <final> - ipad : byte = 0x36;<static> <final> - keyLen: byte = 64;static> - generateKey(key:String)

    代码:

        /**
         * 处理密钥.
         * <p>密钥补为64字节;长于64字节的用md5散列结果.</p>
         * @param key 签名密钥.
         * @return 结果.
         */
        private static byte[] generateKey(String key) {
    
            ByteBuffer buf = ByteBuffer.allocate(keyLen);
            if(key.length()>keyLen){
                buf.put(Md5Utils.md5Bytes(key.getBytes(StandardCharsets.UTF_8))) ;
            }else{
                buf.put(key.getBytes());
            }
    
            return buf.array();
    
        }
    

    "istr“和”ostr"的生成

    istr和ostr是密钥数组分别和ipad和opad常数做异或得到的结果。类图如下:

    AbstractHMacMd5UtilsHMacMd5Utils<static> <final> - opad : byte = 0x5c;<static> <final> - ipad : byte = 0x36;<static> <final> - keyLen: byte = 64;static> - generateKey(key:String)static> - generateIstr(key: byte[])static> - generateOstr(key: byte[])
    • generateIstr(key:byte[]): byte[]的实现如下:
        /**
         * 生成istr数据.
         * @param key 签名密钥.
         *            <p>64字节长.</p>
         * @param out 输出数组.
         * @return 结果.
         */
        public static byte[] generateIstr(byte[] key, byte[] out){
            for(int i=0;i<keyLen;i++){
                out[i]=(byte)(key[i]^ipad);
            }
            return out;
        }
    
    • generateOstr(key:byte[]): byte[]的实现如下:
     /**
         * 生成ostr数据.
         * @param key 签名密钥.
         *            <p>64字节长.</p>
         * @param out 输出数组.
         * @return 结果.
         */
        private static byte[] generateOstr(byte[] key, byte[] out) {
            for(int i=0;i<keyLen;i++){
                out[i]=(byte)(key[i]^opad);
            }
            return out;
        }`
    

    签名的实现

    在以上内容实现之后,签名过程的开发就相对简单了,流程如下:

    Created with Raphaël 2.2.0开始: 参数key为密钥文本;参数data为要加密的数据<<参数>> key:String // 密钥文本<<参数>> data: byte[] // 签名数据调用generateKey(密钥文本)方法,生成密钥数据k变量k:密钥:byte[]变量data1:byte[] - 第一次md5计算的输入数据计算istr,并将istr写入data1中将参数data继续写入data1中计算data1的md5值,并保存在变量md5One中变量md5One:byte[] //第一次md5计算结果变量data2 //第二次md5计算的输入数据计算ostr并写入data2中计算第二次md5结果res返回值 res:byte[] 第二次md5计算的结果结束

    代码如下:

        /**
         * 数据签名.
         * @param key 签名密钥.
         * @param data 数据.
         * @return 签名.
         */
        public static byte[] sign(String key, byte[] data){
    
            byte[] k = generateKey(key);
            byte[] str = new byte[keyLen];
    
            byte[] data1 = new byte[keyLen+data.length];
            System.arraycopy(generateIstr(k, str), 0, data1, 0, keyLen);
            System.arraycopy(data, 0, data1, keyLen, data.length);
            byte[] md5One = Md5Utils.md5Bytes(data1);
    
            byte[] data2 = new byte[keyLen+md5One.length];
            System.arraycopy(generateOstr(k, str), 0, data2, 0, keyLen);
            System.arraycopy(md5One, 0, data2, keyLen, md5One.length);
    
            return Md5Utils.md5Bytes(data2);
        }
    

    这里特别的地方是str这个变量,用来保存istr和ostr。因为这两个变量不是同时使用的,所以用了同一块内存来保存,不用分配两个字节数组。虽然对于java来讲这么操作不会有什么影响,不过还是尽量节约内存,养成良好习惯。

    为了调用方便,我们增加两个常用接口:

    
        /**
         * 生成签名字符串.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 签名.
         * <p>Hex格式, e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
         */
        public static String signHex(String key, String operatorID, String data, String timestamp, String seq){
            return HexUtils.bytes2HexString(sign(key, operatorID, data, timestamp, seq));
        }
    
        /**
         * 生成签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         */
        public static byte[] sign(String key, String operatorID, String data, String timestamp, String seq){
            return sign(key, assembleData(operatorID,data, timestamp, seq, StandardCharsets.UTF_8));
        }
    

    类图如下:

    AbstractHMacMd5UtilsHMacMd5Utils<static> <final> - opad : byte = 0x5c;<static> <final> - ipad : byte = 0x36;<static> <final> - keyLen: byte = 64;static> - generateKey(key:String)static> - generateIstr(key: byte[])static> - generateOstr(key: byte[])static> + sign(key: String, data: byte[])static> + sign(key: String, operatorId:String, data:String, ts:String, seq:String)static> + signHex(key: String, operatorId:String, data:String, ts:String, seq:String)

    测试过程

    测试过程用JUnit实现,代码如下:

        String want = "745166E8C43C84D37FFEC0F529C4136F";
        String key = "1234567890abcdef";
        String opId = "123456789";
        String data = "il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=";
        String ts = "20160729142400";
        String seq = "0001";
    
        @Test
        public void test(){
            TestCase.assertEquals(
                    want, HMacMd5Utils.signHex(key, opId, data, ts, seq)
            );
        }
    
    

    尚未实现的功能

    HMAC-MD5作为一个签名算法,当然应该有校验的过程。不过,我们这里暂时不实现校验,因为后面还有其他的工作。如果有需要,可以尝试自己实现校验过程。

    JCE接口实现

    JCE(Java Cryptography Extension)是Java的加密算法支持,提供了统一的加密、解密、散列、签名、密钥算法。例如,我们MD5工具类就使用了JCE的实现,如下:

       /**
         * 数据的md5.
         * @param data 输入字符串.
         * @return md5.
         */
        public static byte[] md5Bytes(byte[] data) {
            try {
                // 生成一个MD5加密计算摘要
                MessageDigest md = MessageDigest.getInstance("MD5");
                //对字符串进行加密
                md.update(data);
                //获得加密后的数据
                return md.digest();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("没有md5这个算法");
            }
        }
    

    我们的HMAC-MD5现在只能作为一个工具类使用。假如有另外一段代码已经用了JCE实现的其他方式签名,现在要改成HMAC-MD5,那么他需要重写算法。而如果我们能够提供JCE接口的HMAC-MD5算法。那么他就能很容易的切换算法。

    密钥

    密钥相关的部分分为几个内容:

    • KeySpec: 用来保存密钥
    • PublicKey:加密公钥
    • PrivateKey:解密私钥
    • Key:密钥
    • KeyFactory: 密钥工厂

    密钥相关类

    1. 首先HMacMd5KeySpec用于保存密钥数据。
    2. 因为HMAC-MD5算法不区分公钥/私钥,所以HMacMd5Key同时实现PublicKey合PrivateKey接口。
    3. HMacMd5KeyFactory类用于生成HMacMd5相关的密钥。

    类HMacMd5KeySpec

    本类代码如下:

    package net.wangds.tcec.tcec102d4.messagedigest.hmacmd5;
    
    import net.wangds.utils.Md5Utils;
    
    import java.nio.ByteBuffer;
    import java.nio.charset.StandardCharsets;
    import java.security.spec.KeySpec;
    
    /**
     * .
     * <p></p>
     *
     * @author wangds 2020/10/20 12:31.
     */
    public class HMacMd5KeySpec implements KeySpec {
    
        private static final int LEN = 64;
    
        ByteBuffer buf = ByteBuffer.allocate(LEN);
    
        public HMacMd5KeySpec(String key){
            this(key.getBytes(StandardCharsets.ISO_8859_1));
        }
    
        public HMacMd5KeySpec(byte[] key){
            buf.position(0);
            buf.limit(buf.capacity());
    
            if(key.length>LEN){
                key = Md5Utils.md5Bytes(key);
            }
    
            buf.put(key);
    
            buf.flip();
            buf.rewind();
    
        }
    
        public static KeySpec of(String s) {
            return new HMacMd5KeySpec(s);
        }
    
        public byte[] toBytes(){
            return buf.array();
        }
    
    }
    
    

    可以看到这个类主要是通过一个字节缓存保存密钥数据。

    类HMacMd5Key

    这个类用于实现PublicKey、PrivateKey接口,是签名、验证过程中设置密钥时所需的对象类型。

    
    package net.wangds.tcec.tcec102d4.messagedigest.hmacmd5;
    
    import java.security.PrivateKey;
    import java.security.PublicKey;
    
    /**
     * .
     * <p></p>
     *
     * @author wangds 2020/10/20 12:45.
     */
    public class HMacMd5Key implements PublicKey, PrivateKey {
    
        private final HMacMd5KeySpec spec;
    
        public HMacMd5Key(HMacMd5KeySpec spec){
            this.spec = spec;
        }
    
        @Override
        public String getAlgorithm() {
            return "HMacMd5";
        }
    
        @Override
        public String getFormat() {
            return "NOPadding";
        }
    
        @Override
        public byte[] getEncoded() {
            return spec.toBytes();
        }
    }
    
    
    

    可以看到,类中关联了一个KeySpec的属性用于保存密钥,并指定改了密钥的名称和格式。

    类HMacMd5KeyFactory

    这里需要特殊说明一下,此类的父类时KeyFactorySpi,而不是KeyFactory。JCE的工厂类会将Spi类包装为Factory类,这个过程不用我们实现,我们实现KeyFacotrySpi就可以了。

    代码如下:

    package net.wangds.tcec.tcec102d4.messagedigest.hmacmd5;
    
    import org.apache.commons.lang3.StringUtils;
    
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.KeySpec;
    
    /**
     * .
     * <p></p>
     *
     * @author wangds 2020/10/20 12:34.
     */
    public class HMacMd5KeyFactory extends KeyFactorySpi {
    
    
        @Override
        protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
            if(keySpec instanceof  HMacMd5KeySpec) {
                return new HMacMd5Key((HMacMd5KeySpec) keySpec);
            }
            throw new InvalidKeySpecException("只支持HMacMd5KeySpec");
        }
    
        @Override
        protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
            if(keySpec instanceof  HMacMd5KeySpec) {
                return new HMacMd5Key((HMacMd5KeySpec) keySpec);
            }
            throw new InvalidKeySpecException("只支持HMacMd5KeySpec");
        }
    
        @Override
        @SuppressWarnings({"unchecked","cast"})
        protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec) throws InvalidKeySpecException {
            if(StringUtils.equals(keySpec.getClass().getName(), HMacMd5KeySpec.class.getName())){
                return (T)new HMacMd5KeySpec(key.getEncoded());
            }
            throw new InvalidKeySpecException("只支持HMacMd5KeySpec");
        }
    
        @Override
        protected Key engineTranslateKey(Key key) throws InvalidKeyException {
            byte[] keyData = key.getEncoded();
            return new HMacMd5Key(new HMacMd5KeySpec(keyData));
        }
    }
    
    

    通过上面代码,我们可以看到KeyFactorySpi这个接口主要是提供Key的生成和转换功能。我们这个代码本身不用考虑Key的那么多兼容性,毕竟应用场景只是行业内,所以在代码中看到异常情况处理基本是抛错提示。

    签名算法实现

    签名算法是通过一个SignatureSpi类的子类HMacMd5SignSpi实现的。如下图:

    SignSpi的实现
    在这个类中,通过engineInitSign()、engineSign()等方法实现了签名过程;通过engineInitVerify()和engineVerify()等方法实现类验证过程。

    算法可参考之前Utils类的算法,这里只提供代码。

    package net.wangds.tcec.tcec102d4.messagedigest.hmacmd5;
    
    import net.wangds.utils.HexUtils;
    import net.wangds.utils.Md5Utils;
    import org.apache.commons.lang3.StringUtils;
    
    import java.nio.ByteBuffer;
    import java.security.*;
    
    /**
     * HMAC-MD5签名算法实现.
     * <p></p>
     *
     * @author wangds 2020/10/20 12:51.
     */
    public class HMacMd5SignSpi extends SignatureSpi {
    
    
        private static final byte opad = 0x5c;
        private static final byte ipad = 0x36;
        private static final byte KEN_LEN = 64;
    
        private Key key;
    
        private ByteBuffer buf = ByteBuffer.allocate(KEN_LEN);
    
        @Override
        protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
            this.key = new HMacMd5KeyFactory().engineTranslateKey(publicKey);
        }
    
        @Override
        protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
            this.key = new HMacMd5KeyFactory().engineTranslateKey(privateKey);
        }
    
        @Override
        protected void engineUpdate(byte b) {
            buf.rewind();
            buf.put(b);
            buf.flip();
        }
    
        @Override
        protected void engineUpdate(byte[] b, int off, int len) {
            if(buf.capacity()<len){
                buf = ByteBuffer.allocate(len);
            }
            buf.rewind();
            buf.put(b, off, len);
            buf.flip();
        }
    
        @Override
        protected byte[] engineSign() {
            return calcSigin();
        }
    
        private byte[] calcSigin() {
    
            byte[] data = new byte[buf.limit()];
            byte[] k = key.getEncoded();
            int keyLen = k.length;
    
            byte[] str = new byte[keyLen];
    
            buf.rewind();
            buf.get(data);
    
    
            byte[] data1 = new byte[keyLen+ data.length];
            System.arraycopy(generateIstr(k, str), 0, data1, 0, keyLen);
            System.arraycopy(data, 0, data1, keyLen, data.length);
            byte[] md5One = Md5Utils.md5Bytes(data1);
    
            byte[] data2 = new byte[keyLen+md5One.length];
            System.arraycopy(generateOstr(k, str), 0, data2, 0, keyLen);
            System.arraycopy(md5One, 0, data2, keyLen, md5One.length);
    
            return Md5Utils.md5Bytes(data2);
        }
    
        @Override
        protected boolean engineVerify(byte[] inSign) {
    
            byte[] calcSign = this.calcSigin();
    
            String hexCalcSign = HexUtils.bytes2HexString(calcSign);
            String hexInSign = HexUtils.bytes2HexString(inSign);
    
            return StringUtils.equals(hexCalcSign, hexInSign);
        }
    
        @Override
        protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
            //skip
        }
    
        @Override
        protected Object engineGetParameter(String param) throws InvalidParameterException {
            return null;
        }
    
    
        /**
         * 生成istr数据.
         * @param key 签名密钥.
         *            <p>64字节长.</p>
         * @param out 输出数组.
         * @return 结果.
         */
        public static byte[] generateIstr(byte[] key, byte[] out){
    
            for(int i = 0; i< KEN_LEN; i++){
                out[i]=(byte)(key[i]^ipad);
            }
            return out;
        }
    
        /**
         * 生成ostr数据.
         * @param key 签名密钥.
         *            <p>64字节长.</p>
         * @param out 输出数组.
         * @return 结果.
         */
        private static byte[] generateOstr(byte[] key, byte[] out) {
            for(int i = 0; i< KEN_LEN; i++){
                out[i]=(byte)(key[i]^opad);
            }
            return out;
        }
    }
    
    

    Provider类

    Provider类是算法注册的接口。通过Provider,将算法注册到JCE中。

    如下:

    package net.wangds.tcec.tcec102d4.messagedigest.hmacmd5;
    
    import java.security.Provider;
    import java.security.Security;
    
    /**
     * .
     * <p></p>
     *
     * @author wangds 2020/10/21 10:52.
     */
    public final class HMacMd5Provider extends Provider {
    
        static {
            Security.addProvider(new HMacMd5Provider());
        }
    
        /**
         * Constructs a provider with the specified name, version number,
         * and information.
         *
         */
        protected HMacMd5Provider() {
            super("HMacMd5", 1.0f, "HMAC-MD5 Provider v1.0");
            put("KeyFactory.HMacMd5", "net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5KeyFactory");
            put("Signature.HMacMd5", "net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5SignSpi");
        }
    }
    
    

    Provider类在使用时有两种方式,一种是通过修改JRE配置文件实现的;另外一种就是和JDBC中数据库驱动类似的方式,通过反射接口东财注入的。

    动态注入的代码片段如下:

        static {
            try {
                Class.forName("net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5Provider");
            } catch (ClassNotFoundException e) {
                LogHelper.error(e);
            }
        }
    

    JCE接口的测试

    代码如下:

    
        /**
         * 测试直接使用jce接口的方式调用.
         */
        @Test
        public void test1(){
    
    
    
            try {
                Class.forName("net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5Provider");
            } catch (ClassNotFoundException e) {
                LogHelper.error(e);
            }
    
            try {
    
                Provider prov = Security.getProvider("HMacMd5");
    
                LogHelper.dev("prov:"+prov);
    
                KeyFactory fac = KeyFactory.getInstance("HMacMd5");
    
                LogHelper.error("fac:"+fac);
    
                Signature sign = Signature.getInstance("HMacMd5");
                LogHelper.error("sign:"+sign);
    
                try {
                    KeySpec keySpec = HMacMd5KeySpec.of(key);
                    PrivateKey pk = fac.generatePrivate(keySpec);
    
                    sign.initSign(pk);
                    sign.update(HMacMd5Utils.assembleData(opId, data, ts, seq, StandardCharsets.UTF_8));
                    byte[] res = sign.sign();
                    byte[] bsSign = new byte[16];
                    System.arraycopy(res, res.length-16, bsSign , 0,16);
                    LogHelper.dev("sign length:"+res.length);
                    String hex = HexUtils.bytes2HexString(bsSign);
                    TestCase.assertEquals(want, hex);
    
    
                    PublicKey pub = fac.generatePublic(keySpec);
                    sign.initVerify(pub);
                    sign.update(HMacMd5Utils.assembleData(opId, data, ts, seq, StandardCharsets.UTF_8));
                    TestCase.assertTrue(sign.verify(res));
    
                } catch (InvalidKeyException e) {
                    e.printStackTrace();
                } catch (InvalidKeySpecException | SignatureException e) {
                    e.printStackTrace();
                }
    
    
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
    
    
        }
    

    注意这里也只测试了加密过程。

    重新封装工具类

    HMacMd5Utils2类

    这个接口通过调用JCE风格的实现,便于以后算法的升级和变换。

    代码如下:

    package net.wangds.cnpg.cpmn.utils;
    
    import net.wangds.log.helper.LogHelper;
    import net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5KeySpec;
    import net.wangds.utils.HexUtils;
    
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.KeySpec;
    import java.util.function.BiFunction;
    
    /**
     * .
     * <p></p>
     *
     * @author 王东石 2020/10/21 11:46.
     */
    public final class HMacMd5Utils2 extends AbstractHMacMd5Utils {
    
        static {
            try {
                Class.forName("net.wangds.tcec.tcec102d4.messagedigest.hmacmd5.HMacMd5Provider");
            } catch (ClassNotFoundException e) {
                LogHelper.error(e);
            }
        }
    
        /**
         * 生成签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @param charset 字符集.
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         */
        public static byte[] sign(String key, String operatorID, String data, String timestamp, String seq, Charset charset){
            return sign(key, assembleData(operatorID,data, timestamp, seq, charset));
        }
    
    
        /**
         * 生成签名Hex字符串.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @param charset 字符集.
         * @return 签名.
         * <p>根据规范要求,字母大写。e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
         */
        public static String signHex(String key, String operatorID, String data, String timestamp, String seq, Charset charset){
            return HexUtils.bytes2HexString(sign(key, operatorID, data, timestamp, seq, charset)).toUpperCase();
        }
    
        /**
         * 生成签名.
         * <p>默认字符集:UTF-8</p>
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         */
        public static byte[] sign(String key, String operatorID, String data, String timestamp, String seq){
            return sign(key, assembleData(operatorID,data, timestamp, seq, StandardCharsets.UTF_8));
        }
    
        /**
         * 生成签名Hex字符串.
         * <p>默认字符集:UTF-8</p>
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
         */
        public static String signHex(String key, String operatorID, String data, String timestamp, String seq){
            return HexUtils.bytes2HexString(sign(key, operatorID, data, timestamp, seq)).toUpperCase();
        }
    
    
        /**
         * 生成签名.
         * @param key 签名密钥.
         * @param data 数据.
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         */
        public static byte[] sign(String key, byte[] data){
            return withSign((fac, sign)->{
                try {
                    KeySpec keySpec = HMacMd5KeySpec.of(key);
                    PrivateKey pk = fac.generatePrivate(keySpec);
    
                    sign.initSign(pk);
                    sign.update(data);
                    return sign.sign();
                } catch (InvalidKeyException|InvalidKeySpecException | SignatureException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    
        /**
         * 生成签名Hex字符串.
         * @param key 签名密钥.
         * @param data 数据.
         * @return 签名.
         * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
         */
        public static String signHex(String key, byte[] data){
            return HexUtils.bytes2HexString(sign(key, data)).toUpperCase();
        }
    
        /**
         * 验证Hex格式签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param sign 签名.
         *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 验证结果.
         */
        public static boolean verifyHex(String key, String sign, String operatorID, String data, String timestamp, String seq){
            return verify(key, HexUtils.hexString2Bytes(sign), operatorID, data, timestamp, seq);
        }
    
        /**
         * 验证签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param sign 签名.
         *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @return 验证结果.
         */
        public static boolean verify(String key, byte[] sign, String operatorID, String data, String timestamp, String seq){
            return verify(key, sign, assembleData(operatorID,data, timestamp, seq, StandardCharsets.UTF_8));
        }
    
        /**
         * 验证Hex格式签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param sign 签名.
         *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @param charset 字符集.
         *
         * @return 验证结果.
         */
        public static boolean verifyHex(String key, String sign, String operatorID, String data, String timestamp, String seq, Charset charset){
            return verify(key, HexUtils.hexString2Bytes(sign), operatorID,data, timestamp, seq, charset);
        }
    
        /**
         * 验证签名.
         * @param key 签名密钥.
         *            <p>e.g.: 1234567890abcdef</p>
         * @param sign 签名.
         *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
         * @param operatorID 运营商Id.
         *                   <p>e.g.: 123456789</p>
         * @param data 数据.
         *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
         * @param timestamp 时间戳.
         *                  <p>e.g.: 20160729142400</p>
         * @param seq 序列.
         *            <p>e.g.: 0001</p>
         * @param charset 字符集.
         * @return 验证结果.
         */
        public static boolean verify(String key, byte[] sign, String operatorID, String data, String timestamp, String seq, Charset charset){
            return verify(key, sign, assembleData(operatorID,data, timestamp, seq, charset));
        }
    
    
        /**
         * 验证数据包.
         * @param key 密钥.
         * @param signData 签名.
         * @param data 数据.
         * @return 验证是否成功.
         */
        public static boolean verify(String key, byte[] signData, byte[] data){
            return withSign((fac, sign)->{
                try {
                    KeySpec keySpec = HMacMd5KeySpec.of(key);
                    PublicKey pub = fac.generatePublic(keySpec);
                    sign.initVerify(pub);
                    sign.update(data);
                    return sign.verify(signData);
                } catch (InvalidKeyException|InvalidKeySpecException | SignatureException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    
        /**
         * 验证数据包.
         * @param key 密钥.
         * @param signData 签名.
         * @param data 数据.
         * @return 验证是否成功.
         */
        public static boolean verifyHex(String key, String signData, byte[] data){
            return verify(key, HexUtils.hexString2Bytes(signData), data);
        }
    
        /**
         * 在密钥工厂和签名算法确定的条件下,执行回到函数.
         * @param callback 要执行的回调函数.
         * @param <T> 回调函数返回值类型.
         * @return 回调函数返回值.
         */
        private static <T> T withSign(BiFunction<KeyFactory, Signature, T> callback){
            try {
                KeyFactory fac = KeyFactory.getInstance("HMacMd5");
                Signature sign = Signature.getInstance("HMacMd5");
    
                return callback.apply(fac, sign);
    
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }
    
    
    }
    
    

    测试

    测试过程代码如下:

    
        @Test
        public void test3(){
    
            TestCase.assertEquals(want, HMacMd5Utils2.signHex(key, opId, data, ts, seq));
            TestCase.assertEquals(want, HMacMd5Utils2.signHex(key, opId, data, ts, seq, StandardCharsets.UTF_8));
            TestCase.assertTrue(HMacMd5Utils2.verifyHex(key, want, opId, data, ts, seq));
            TestCase.assertTrue(HMacMd5Utils2.verifyHex(key, want, opId, data, ts, seq, StandardCharsets.UTF_8));
    
        }
    
    

    END

    展开全文
  • 为了增加接口的安全性(防止中间人攻击),现增加签名算法。此算法参考微信支付中的签名算法,由于该签名针对前后端,采用了对称算法,如后续接口供给多家第三方接口使用可采用非对称算法。大致整理文档供后续开发...

    一.背景

       为了增加接口的安全性(防止中间人攻击),现增加签名算法。此算法参考微信支付中的签名算法,由于该签名针对前后端,采用了对称算法,如后续接口供给多家第三方接口使用可采用非对称算法。大致整理文档供后续开发人员使用阅读。

    二.  签名生成步骤

    ①设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    注意如下规则:

    ◆参数名ASCII码从小到大排序(字典序);

    ◆如果参数的值为空不参与签名;

    ◆参数名区分大小写;

    ◆验证调用返回或主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。

    ②在stringA最后拼接上key(密钥)得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

    样例:假设传送的参数如下:

    第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下

    stringA = "MEMBER_ID=1&body=test&mch_id=1&order_id=20";

    第二步:拼接API密钥(比如key:192006250b4c09247ec02edce69f6a2d)

    stringSignTemp = stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为密钥

    signValue = md5(stringSignTemp).toUpperCase(); //用MD5加密完后,并且都转为大写

    最终打包的数据为(可以就拿这组数据测试):

    {"MEMBER_ID":1,"body":"test","mch_id":1,"order_id":20,"sign":4AC10199EFB3D9BF4959DFEA83900ACA}

    三.  下面是我整理的JS生成签名的代码,php验证签名的代码

    //JS生成签名源码
    <script src="md5.min.js"></script> //这个网上自己下载
    <script>
    
        var paramsObj = { order_id: 20, mch_id: 1, body: 'test', MEMBER_ID: 1 };//要传的参数(测试数据)
        var key = '192006250b4c09247ec02edce69f6a2d'; //密钥
        var sign = getSign(paramsObj); //获取签名sign
        console.log(sign);  //输出签名
    
        function getSign(params) {
            var arr = [];
            for (var i in params) {
                arr.push((i + "=" + params[i]));
            }
            return paramsStrSort(arr.join(("&")));
        }
    
        function paramsStrSort(paramsStr) {
            var urlStr = paramsStr.split("&").sort().join("&");
            var newUrl = urlStr + '&key=' + key;
            return md5(newUrl).toUpperCase();
        }
    
    </script>
    //php服务端验证签名源码
    <?php
    
    class Sign
    {
        public $key = '192006250b4c09247ec02edce69f6a2d'; //密钥
    
        /**
         * 验签
         * @param $params
         * @return bool
         */
        public function validateSign($params) {
            $stringA = $this->paramFilter($params);
            $sign = $this->md5Sign($stringA);
            if(!isset($params['sign']) || empty($params['sign']) || $params['sign'] != $sign) {
                return false;
            } else {
                return true;
            }
        }
    
        /**
         * 除去数组中的空值和签名参数
         * @param $param
         * @return array 去掉空值与签名参数后的新签名参数组
         */
        function paramFilter($param) {
            $para_filter = array();
            while (list ($key, $val) = each ($param)) {
                if($key == "sign"  || $val == "") continue;
                else    $para_filter[$key] = $param[$key];
            }
            return $this->argSort($para_filter);
        }
    
        /**
         * 对数组排序
         * @param $param
         * @return mixed 排序后的数组
         */
        function argSort($param) {
            ksort($param);
            reset($param);
            return $this->createLinkString($param);
        }
    
        /**
         * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
         * @param $para
         * @return bool|string 拼接完成以后的字符串
         */
        function createLinkString($para) {
            $arg  = "";
            while (list ($key, $val) = each ($para)) {
                $arg.=$key."=".$val."&";
            }
            //去掉最后一个&字符
            $arg = substr($arg,0,count($arg)-2);
            //如果存在转义字符,那么去掉转义
            if(get_magic_quotes_gpc()){
                $arg = stripslashes($arg);
            }
            $arg = $arg.'&key='.$this->key;
            return $arg;
        }
    
        /**
         * 生成md5签名字符串
         * @param $preStr string 需要签名的字符串
         * @return string 签名结果
         */
        function md5Sign($preStr) {
            return strtoupper(md5($preStr));
        }
    }
    
    

     

    展开全文
  • JNI实现获取apk签名MD5

    2018-11-19 15:11:29
    通过jni的方式获取android应用的签名md5值,可以在so中作安全验证
  •  logger.warn("md5签名校验失败,预期的签名->" + expectSign);  }  return checkResult;  }  public static String getSign(Map, Object> parameterMap) {  List<String> keys = new ArrayList...
  • 1、安全服务的类型在ISO 7498-2:1989文档中,定义了安全服务的类型,安全服务包括五种类型:鉴别(Authentication):保证通信的真实性;访问控制(Access Control):防止网络资源被非授权地访问;数据保密性(Data ...
  • 两个不同的且均可正常运行的exe程序文件,具有相同的MD5...我所知的MD5应用最普遍的是在两个方面,一个是文件签名(多用于文件下载等),一个是密码保存。MD5算法在保存密码方面应用非常广泛,许多流行的应用系统都...
  • 安全架构-md5算法介绍

    千次阅读 2020-12-21 14:09:02
    二、特点三、作用四、应用场景4.1 一致性验证4.2 数字签名4.3 安全访问认证五、md5破解 一、md5是什么? MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个...
  • 现在应用开发中通常会用到接口,其数据是通过开放的互联网传输,对数据的安全性有一定要求,为了防止数据在传输过程中被篡改,常用数据签名(sign)的方式来校验。数据签名sign生成方法①去除数组中的空值和签名参数...
  • SHA1和MD5安全性探讨

    千次阅读 2018-08-23 14:42:40
    探讨这个话题是因为山东大学的王小云教授通过碰撞法攻破了SHA1和MD5算法。其成果已经被Crypto大会中的科学家所认可。   SHA和MD系列算法是一种基于散列算法的单向加密算法,也就是说明文一经加密(散列),密文就不...
  • 这种MD5签名算法到底是什么??? 请教接过的人。谢谢。
  • 获取APK的签名MD5

    2016-01-06 11:45:04
    近日在360平台上架app,安全等级居然是0分,代码已经混淆了。...不过最后没有和MD5值比较。下面是我结合网上的其他代码写的: /** * 校验应用签名 * * @param context */ public static boolean checkS
  • 而生成签名,则需要对排序后的字符串进行md5加密。然而在微信小程序中,没有自带的md5加密函数,于是我便在百度上寻找了以下代码:/* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message ...
  •  MD5还广泛用于操作系统的登陆认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方面。如在Unix系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在文件系统中。当用户登录的时候,系统把用户输入...
  • openssl实现md5加rsa签名

    千次阅读 2011-09-01 21:54:15
    RSA 的安全性  RSA的安全性依赖于大数分解,但是否等同于大数分解一直未能得到理论上的证明,因为没有证明破解 RSA就一定需要作大数分解。假设存在一种无须分解大数的算法,那它肯定可以修改成为大数分解算法...
  • 为了防止拖库,数据库中不能存储明文密码,将密码进行“加密...(MD5 Message-Digest Algorithm),一种常用的哈希算法,用于给任意数据一个“签名”。这通常用一个十六进制的字符串表示。 1、给密码简单“加密” ...
  • 知道是一个http类型的post请求,首先需要获取时间戳(time),然后把appid、body、accessToken、time数进行MD5加密处理生成sign,然后把该参数传到信息头实现鉴权,使用body参数做指纹签名,可以提高安全性 ...
  • 高德地图用户MD5安全码未通过

    千次阅读 2016-12-18 18:00:57
     无意间想起直接运行到手机上的签名跟打包后的签名是不一样的,直接运行程序到手机上的签名是系统自动生成的,签名的具体路径忘记在哪里了,而打包后的签名是我们自己填写设置好的。  所以我们用打包时的签名去...
  • 摘要:很多安全规范及安全文章中都提到一条规则:先加密后签名是不安全的,应当先签名后加密。这条规则背后的原理是什么?先加密后签名一定不安全吗?本文为您一一解答。先签名后加密是指先对消息进行签名,然后对...
  • 最近使用高德地图的时候,做了个demo,查看公交车线路的时候,跳出“用户MD5安全码未通过”,最后发现是SHA1值的问题两个SHA1值都要填写,下面我来说一下如何获取两个SHA1值发布版本安全码 SHA1值获得:Android ...
  • 这一段是原创,说得不对的地方还请支出(: 首先要分清楚MD(Message Digest 信息摘要)5(第五代)和SHA1(Secure Hash Algorithm 安全... Algorithm),即将无限制长度的字符串转换成固定长度(MD5是16个字符16*8=128...
  • api接口签名验证(MD5)

    千次阅读 2018-12-17 10:34:01
    你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如: 请求来源(身份)是否合法? 请求...
  • 接口数据加密 1. 对我们的接口实现对称加密,抓包的时候看不到明文的数据,但是可以被破解,因为客户端与服务器都...可以采用MD5对我们的参数实现验证签名,但是数据还是传输明文,可以防止篡改数据。 使用令牌 在..
  • 两个不同的且均可正常运行的exe程序文件,具有相同的MD5校验值,这不... 我所知的MD5应用最普遍的是在两个方面,一个是文件签名(多用于文件下载等),一个是密码保存。 MD5算法在保存密码方面应用非常广泛,许多流行
  • 一.... 登录机制大概可以分为一下三个阶段: ...2. 登录保持:是指客户端登录后, 服务器能够分辨出已登录的客户端,并为其持续提供登录权限的服务器...第一种网络请求情况(安全级别:II) 一般的情况是这个样子的:一...
  • md5() 函数使用 RSA 数据安全,包括 MD5 报文摘要算法。来自 RFC 1321 的解释 - MD5 报文摘要算法:MD5 报文摘要算法将任意长度的信息作为输入值,并将其换算成一个 128 位长度的"指纹信息"或"报文摘要"值来代表这个...
  • 今天遇到一个需求,获取所有apk的签名MD5,下面是我使用Java SE实现的一个工具,贴出核心源代码,希望给有需要的朋友有所帮助。界面如下:只需要制定.apk文件所在的目录即可,核心代码如下:public class ...
  • MD5

    2016-07-27 11:19:31
    MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。除了MD5以外,其中比较有名的还有sha-1、RIPEMD以及Haval等。 应用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 892
精华内容 356
关键字:

md5安全签名