证书链设计

加密

加密(英语:Encryption)是将明文信息改變為難以讀取的密文內容,使之不可读的过程。

  • 不可逆加密算法
      例如:MD4,MD5,HASH,
  • 可逆加密算法
    • 对称加密
        DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法
    • 非对称加密
        RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。
        使用最广泛的是RSA算法,Elgamal是另一种常用的非对称加密算法。
      RSA原理
      AES原理

证书链

20180530103403964.png
  • 授权
    • 生成head
    • 生成证书链Message+DN(RSA+SHA:父证书的privateKey)+publicKey(父证书);
    • AES加密+BASE64编码;

ROOT证书 check> 中间证书 check> 子证书

图片1.png

  • 证书格式
    input
  {
    "company": "CompanyNameHere",
    "company_id": CompanyIDHere,
    "type": "redistribute || leaf",
    "product": "ProductNameIncludedInPorductList",
    "model_secret": true,
    "capability": {
      "track": true
    },
    "limit": {
      "uuid": "test_uuid",
      "expiration": [20160501, 20160630],
      "appid": ["com.example.app1", "com.test.*"],
      "cores": 4,
      "_hardware_auth": "athsa204a",
      "_hardware_auth_key": "YWQwYWE0NWVhM2Y5MDNkOWM4ZDQxN2U4NmRmMDU1MzE="
    },
    "counter": {
      "threads": 4
    }
  }

output

############################################################
#  YinLib License
#  License Product : ProductNameIncludedInPorductList
#  Expiration : 20160501 ~ 20160630
#  License SN : 63f09206-1c46-4247-8788-ba9b16bb1994
############################################################
GWfTA0kPlx4+THgLJKZByzgtewJg2AML0Drr33qTzZkTTeuB0K3n6jVVITsY
ocJxd2w45pIX9GVdbHXl/aaeidWWDLcXAYsig05k34PbxarmdJvLJYUTHjk0
EN3r+L6sgutdl974QSX+qHqD60fTNu6F801evs9DXBAs2dWphwG0AxJ9IJIi
npE2nFXJBSfSJ3T7nS157hjF59Y/6xd/1GbGlFZe5onSIZZKvaV3oxHFL6kf
J6a/1mrHnghQwb4D9csXoEK5ky+3lIsMNGKpZeZanEMm/GUN5fDjuZ6TWj6B
FjECjIRtHgFvIaj6+FLskoQgFeuPhMCiAuYqm20e/8UJ8Ay51RMsQ/fgLEI3
v/FAbK6i1B+hKgUk6z8C1XkPeEtF439EF1W+ubj0Z/IOKzyCk87Hcp4GXODD
2N/tlVLjBZ6qhI1jxwbcYYu6HKEM1VpxVJawZgA4YkyNU9LmsKkLLuiKniJz
g1OuqNLkVpgkGiSnpSoZFXTk9OQW7ZXP6mLZA7/HiivILEraP1Wfk4CCayCF
oxxTZpzeQNA5CXWVp4yXNtJdXo2qeX/9AyfQ3PHWdKdYD1aXTldV0tGixOGc
6ayGpbfy6nuZSIFSrko4BEH/VDqMy2orGhyfUMUmfeXa+TnyIi3iFx8yuP2B
1z0U1x/yILc26FePSzdZLWaZLCgBldl9N5nfKvUFrAxHiOK3rtMryokCuS0j
DcKWyPGxiOy5ePFkhrlpDXycngVNF8QhZxy22OLe4VYwWnByGqiChg89t2Gh
x7gWMd+Z9GDmwrnHQ9q/hT8Awo7L1nPNKxY4+xXVq/rg+4ASFeWiXhw0/Q3D
gSluEQy8kmmarnRrgEc1QzZ+lp9Tiemnt64q9u1qSWjqJ1moies/3ynqZA68
7vo0YxGNmKfUW4vQaHfi05AcMtHI2D7c8L8Hnl0=
------------------------------------------------------------
GWfTA0kPlx4+THgLJKZBy9BEimQkYd4jMzDHUnA2hOgCEOSPfmWLlpp7n4eY
5ED5eYLHOB6SXNf+LFyK1GnCgU1bSCzYVA1LRkOmdfGiLvu+iXsTsci8mKea
fsyR9IhbVBhQWqLrBi53QHXIQ9WmF+6F801evs9DXBAs2dWphwG0AxJ9IJIi
npE2nFXJBSfSMZQVOv0nFLV2J4GU5nlAqsebocwKBpJPMlf1FICvADg0imwG
yoTQHuMLaPDWD9kNxl/y8hREC9oUQODmE1yAxl7cPQUpsrCyGcrvDqX/RwYD
R0yqZQLHZrtkqhM1HZ4bmAd6c3ve7Rj/u+/FAl5/LSNvl3vzzUzmAqjytlM3
Gr6BFjECjIRtHgFvIaj6+FLsKSHZE9iSLPXRjcD15LSy44FsbiA9Vg459tKj
YeuyKbbbyq3KvDrGAlMQ0l361YG+puCliofN0P/j13AoElrPYjU4YGp5lXYm
Pn1zYqcAXWMGEVK7iSg4+F4aNqVc5lAcVx8oehIc3zUBsRV6L4fLgtjAWdZB
LNqMn1t0QyQTad44bKZL6m0kYhfhiLJoI9NHQhCDnotyhcV1ELtQtX3hwd+C
3T/HQz8ECurhKA1nvA7TIq8ToKfUUdqspLXDJ+Tk6j4TaZKjqFMrwCNX1tgt
ers05ckKPEX36BSVVCPTkjirVuwkBRp4bLe2Sy4V+NjA8I+jkBOfv9qQkH6w
nYW5C6pkOvPNgZRzMYWk+eDNGG1BB+5ngXxe8u21KhjjUZOdUzXQlA9Gt00O
oF9shltdl3RR1j/Ub9wso23AxuBW4I+zetETUARucq0RJTqWBCuwELfqQM/6
sGmNA5yhj/ggMPG8uJ5kp5nZXz1sx2QvmKK2LIEhYHEMya0/9ml8tmF1RKsK
8lvpzKcSvNIKOlu0EoHn2XGyrGvLDwzHv1ttffN0akqSCvcpl/5vDHCRhA6T
h0u63ELA4QYExYWVNwFY3xXcLfMQfh1BVUbQm3SZIWY=
############################################################

code AES+RSA加密生成License:

public static final String encrypt(String content, String privateKey, String publicKey, String aesKey) throws Exception{
        if(!StringUtils.isNotEmpty(content) || !StringUtils.isNotEmpty(privateKey) || !StringUtils.isNotEmpty(publicKey) || !StringUtils.isNotEmpty(aesKey)){
            return null;
        }
        content = StringUtils.replaceBlank(content);
        String rsaContent = encrypt(content, privateKey, publicKey);
        System.out.println("encrypt rsa content : " + rsaContent.length());
        String encryptContent = AES.encryptToBase64(rsaContent, aesKey);
        System.out.println("encrypt aes content : " + encryptContent);
        return encryptContent;
    }
    
    public static final String encrypt(String content, String privateKey, String publicKey) throws Exception{
        if(!StringUtils.isNotEmpty(content) || !StringUtils.isNotEmpty(privateKey) || !StringUtils.isNotEmpty(publicKey)){
            return null;
        }
        String rsaData = RSA.sign(content, privateKey);
        LicenseContent licenseContent = new LicenseContent();
        licenseContent.setMessage(content);
        licenseContent.setPublicKey(publicKey);
        licenseContent.setMessageDigest(rsaData);
        String objectStr = StringUtils.replaceBlank(sGson.toJson(licenseContent));
        System.out.println("encrypt rsa content : " + objectStr);
        return objectStr;
    }

Format

    public static String generateLicense() {
        GenerateService.generateRootKeyPare();
        GenerateService.generateSubKeyPare();
        String rootContent = FileUtils.read("root.lic");
        String rootLic = generateRootLicense(rootContent);
        String subContent = FileUtils.read("sub.lic");
        String subLic = generateSubLicense(subContent);
        String head = generateHead(subContent);
        String content = head + StringUtils.formatContent(rootLic) + StringUtils.contentSplit() + StringUtils.formatContent(subLic) + StringUtils.headSplit();
        System.out.println("License format : \n" + content);
        return content;
    }
  • 验证
    • 解析
    • 验证签名
    • 验证权限

解析:License解析
AES:

    public static String decryptFromBase64(String data, String key){
        try {
            byte[] originalData = Base64.decode(data.getBytes());
            byte[] valueByte = decrypt(originalData, key.getBytes(ConfigureEncryptAndDecrypt.CHAR_ENCODING));
            return new String(valueByte, ConfigureEncryptAndDecrypt.CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

解析后的内容:数据+数据签名+publicKey

{
    "messageDigest":"N4JeSlY1uaBlFggO96O6OdLmkekkAs0QTClSbb4fEvcs7cQiTw7vHctmGsyMvz1laLx7hXRPEdUB2b9oeZJilQ\u003d\u003d",
    "message":"{\"company\":\"yinlib\",\"company_id\":10001,\"type\":\"redistribute\",\"product\":\"rsatest\",\"model_secret\":false,\"capability\":{\"track\":true},\"limit\":{\"uuid\":\"test_uuid\",\"expiration\":[20160501,20160630],\"appid\":[\"com.example.app1\",\"com.test.*\"],\"cores\":4,\"_hardware_auth\":\"athsa204a\",\"_hardware_auth_key\":\"YWQwYWE0NWVhM2Y5MDNkOWM4ZDQxN2U4NmRmMDU1MzE\u003d\"},\"counter\":{\"threads\":4}}",
    "publicKey":"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIH+jdyjYL7wBeea08YRYky5mV4mSlVx6rIfMECyeb9JE4lUaPBza3p1sq4oFUp01Ke4ei+XKJRIR7rjD6PrfWUCAwEAAQ\u003d\u003d"
}

{
    "messageDigest":"TnLSOAzv+UVFxw+7htXiBToQlw3SLj7PnBSwY2Y0SWW8Kl9ITg94gsQ1LXBok5JWEAkhdpH0fQGrI/AYdyR97g\u003d\u003d",
    "message":"{\"company\":\"CompanyNameHere\",\"company_id\":CompanyIDHere,\"type\":\"redistribute||leaf\",\"product\":\"ProductNameIncludedInPorductList\",\"model_secret\":true,\"capability\":{\"track\":true},\"limit\":{\"uuid\":\"test_uuid\",\"expiration\":[20160501,20160630],\"appid\":[\"com.example.app1\",\"com.test.*\"],\"cores\":4,\"_hardware_auth\":\"athsa204a\",\"_hardware_auth_key\":\"YWQwYWE0NWVhM2Y5MDNkOWM4ZDQxN2U4NmRmMDU1MzE\u003d\"},\"counter\":{\"threads\":4}}",
    "publicKey":"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIH+jdyjYL7wBeea08YRYky5mV4mSlVx6rIfMECyeb9JE4lUaPBza3p1sq4oFUp01Ke4ei+XKJRIR7rjD6PrfWUCAwEAAQ\u003d\u003d"
}

验证签名:验证License是本人签发,没有被篡改

    private static boolean checkLicenseChain(String[] licenseChain) {
        if(licenseChain == null || licenseChain.length == 0){
            return false;
        }
        int length = licenseChain.length;
        List<LicenseContent> licenseContents = new ArrayList<LicenseContent>(length); 
        List<LicenseInfo> licenseInfos = new ArrayList<LicenseInfo>(length); 
        for(int i = 0; i < length; i++){
            String content = AES.decryptFromBase64(licenseChain[i], AESKEY);
            LicenseContent licenseContent = sGson.fromJson(content, LicenseContent.class);
            System.out.println(content);
            if(licenseContent == null){
                return false;
            }
            licenseContents.add(licenseContent);
            boolean isSignCheckPass = RSA.checkSign(licenseContent.getMessage(), licenseContent.getMessageDigest(), licenseContent.getPublicKey());
            if(!isSignCheckPass){
                return false;
            }
            LicenseInfo licenseInfo = sGson.fromJson(licenseContent.getMessage(), LicenseInfo.class);
            if(licenseInfo == null){
                return false;
            }
            licenseInfos.add(licenseInfo);
            System.out.println("[" + i + "] : " + isSignCheckPass);
        }
        boolean status = checkLicenseChainPermission(licenseInfos);
        return status;
    }

验证权限:验证License的权限

    private static boolean checkLicenseChainPermission(
            List<LicenseInfo> licenseInfos) {
        //check head time / product
        //check child license time/appid/count and so on permission
        return true;
    }

缺陷

  • AES Key是固定的,容易泄露;
  • AES Key泄露后容易被伪造;

改进策略

  • AES Key随机,并由父证书的PrivateKey加密,Root的publicKey隐藏(不公开),解析时,由root publickey开始逐级解析出AES Key和publickey;
  • AES Key随机秘钥 + 内容的MD5或者其他hash值,重新生成秘钥,验证是验证新秘钥是否包含内容的MD5或者Hash值;
  • 建立父子证书依赖关系,互相验证关系;

证书链设计Code:GitHub

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 之前的项目中接触过一些加密的方法,也没有太仔细的进行记录和研究。最近在写SDK时,加密模块的占比相当之大;借此时机...
    过半_e764阅读 550评论 0 0
  • 文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现...
    梅_梅阅读 277评论 0 0
  • 本文摘自 腾讯bugly 的文章《全站 HTTPS 来了》,内容有修改。 大家在使用百度、谷歌或淘宝的时候,是否注...
    bnotes阅读 3,629评论 1 9
  • iOS 侧滑返回详解 BBGestureBack iOS 全屏手势返回 滑动返回 pop 动画效果 这种手势主流A...
    Bonway_Huang阅读 783评论 6 0
  • 我就像冬日里的残枝,渴望着来春的重生。可我却不能,因为枝头已被折断,掉落在冰冷的土壤上,被肆意的践踏,成为了一根朽木。
    LOSEFIND阅读 282评论 0 0