时间:2017-05-15

密码学在互联网业务中的点滴使用

2017-05-15 宜人贷安全应急响应中心 宜人贷安全应急响应中心

互联网企业在业务交互的过程中常常涉及到各类安全问题。负责任的互联网企业在设计业务服务的过程中常需要引入安全体系以保证线上业务安全。当前SDL在具备一定规模的互联网公司已经被开展实践,本文站在SDL威胁建模的角度表述密码学在其中所解决的问题。

威胁建模常用的STRIDE 模型如下图:

其中的S、T、R、I均与密码学直接强相关。

本文按身份认证、完整性校验、加密、签名四部分分别阐述密码学在互联网业务中所起的作用,并附简单的流程用法。

身份认证

基于挑战/应答的身份认证方式属于密码鉴别的一种,特点是密码不在网络上传输,该认证方式引入随机挑战码,可抗重放攻击。

流程如下图所示:

步骤描述:

1:客户端发起身份验证请求,上送username(密级高的可上送hash值)。

2、3:服务端验证username合法性,如果是合法用户则生成随机挑战码,并下发至客户端(注意撞库)。

4:客户端利用采集的passwd和所得挑战码做双次hash,并与username一起发送给服务端。

5:服务端采用相同算法做身份验证,验证通过则完成认证。

再说说双因素认证:

双因素比较常见的当属基于UKEY的强身份认证,银行通常会为储户提供免费的UKEY设备,以供储户自助网银业务,UKEY属于双因子强身份认证方式,和金立手机一样内置安全芯片,做人做事安全第一;安全芯片存储用户私钥和证书文件,认证通常采用kerberos等认证协议。

但此类外置设备的双因素方案在互联网公司很难落地,强如阿里系支付宝,也没见强推支付盾,原因有二:

1、成本因素

UKEY有设备成本,这部分成本是用户承担还是企业承担?

认证需要的数字证书所依赖的CA系统建设维护成本很高;

2、便利性

移动互联网时代,让用户随身带个Ukey,找个U口插入,真是要了亲命了。

完整性校验&信息加密

完整性校验用来防tampering(数据篡改)。

信息加密用来防information disclosure(信息泄露)。

完整性校验通常采用计算报文mac、哈希值、hmac等,这里把完整性校验和信息加密放在一起讲,主要是因为要介绍一款相对较新的AES CCM加密模式。

AES CCM是CTR加密模式和CMAC消息完整性验证模式的混合模式,常用在需要同时加密和完整性校验的场景;一种模式解决两个问题,多花点时间了解掌握还是很值得。

CCM-CMAC:

CMAC计算所得的消息完整性校验码为AES CBC模式最后一个数据块运算结果的输出,截取其中指定位数作为MAC值;熟悉CBC模式的肯定是秒懂。

CCM-CTR:

CTR模式不需要padding补长,可作为流示加密算法使用,通过加密后的数据可推知原文数据长度,流示加密为后续的业务判断数据字段长度是否合理提供了不少便利,以判断手机号长度举例,13000000001这个手机号,经aes-ctf加密后,所得数据为“90e5462e56467645cebf27”,同样也是11个字节。

另外,由于CTR模式的每次计算依赖动态counter,每次加密运算所得数据均为动态变化,这也保证了数据的随机性。

AES-CTR模式提供python脚本如下:

------------------------------

from Crypto.Cipher import AES
import hashlib
import binascii
import os

password='hello'
plaintext = "13000000001"

def encrypt(plaintext, key, mode, iv):
   encobj = AES.new(key, AES.MODE_CTR, counter = lambda : os.urandom(16))
   return(encobj.encrypt(plaintext))

key = hashlib.sha256(password).digest()
iv= hex(10)[2:8].zfill(16)
print "IV: "+iv

ciphertext = encrypt(plaintext,key,AES.MODE_CTR,iv)
print "Cipher (CTR): "+binascii.hexlify(bytearray(ciphertext))

每次的执行结果如下:

Cipher (CTR): 76b22633a68e04e7982721

Cipher (CTR): e67e9d3805f36f0250899e

2.1 传输安全

https已经是业内标配了,但不意味着https下都是安全,当前互联网公司所采用的https模式都是单向ssl,即服务端侧部署证书,见过不少公司产品客户端不严格校验或者压根不校验服务端证书的情况,中间人攻击比较容易发生。

本文所说的传输安全是指在https隧道内,再实现一次交换密钥的过程,利用交换密钥针对业务数据做密文处理。

交换密钥流程如下图:

步骤描述:

1、client端生成随机数,作为ckey,并用预置rsa公钥加密ckey,生成cmsg;

2、client端上送cmsg至server端;

3、server端使用预置的rsa私钥解密cmsg,得到ckey;

4、server端生成随机数skey,使用ckey分别加密当前连接的sessionid和skey,生成smsg;

5、server端回传smsg;

6、client端用ckey解密smsg,得到skey和sessionid;

7、client端针对拼接的ckey、skey和sessionid做md5,所得数据为后续的数据传输密钥。

2.2 加密存储

数据在数据库中密文存储,数据分类、分级,不同类型、密级数据采用不同的密钥、算法做加密处理。

流程说明:

1、client端采集数据;

2、client端交换密钥,数据加密传输;

3、server端传输密钥脱密数据;

4、server端按约定对数据分级采用不同类别DB存储密钥转加密数据;

5、存库处理。

数字签名

理论上最优的抗repudiation抵赖手段是数字签名。

由权威CA机构颁发的签名数字证书具有法律约束,政务系统中常见的电子签章就是数字签名技术的应用。

数字签名抗抵赖的理论基础是用户为签名私钥的持有者,用户妥善保存私钥,用户所持有签名证书为权威CA机构颁发。

但是,当前互联网公司业务几乎没有给用户签证的,数字签名抗抵赖在互联网公司也就是理论上的可能。

其他类似审计、时间戳等功能,也只是提供抗抵赖的辅助手段。

另外,签名并不是RSA 私钥针对某数据做运算这么简单。

在PKI体系中,证书是密钥的载体,证书合法性和密钥用法都有明确具体要求。密钥用法为“加密”的RSA密钥对是不可以做签名操作的,即使做了后续的验签流程也会失败。

当然,大部分互联网公司的研发是不会care这些的。

密码学防脱库

不是真的防脱库,不过保护数据库连接串也是保障数据库安全的一个维度。

经过安全处理的数据库连接配置文件,再也不用担心敏感信息经github泄露。

太频繁的给数据库更改密码业务方一定不会接受,数据库连接文件中暴露用户名密码又存在太大的安全隐患,数据库连接配置文件关键字段密文处理是个折中的方案。

方案如下:

以jdbc.properties为例:

---------------------------------------------------------

jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:test

#连接数据库的user

jdbc.username=dbadmin

#user对应的密码

jdbc.password=dbpasswd

jdbc.min_connections=1

jdbc.max_connections=5

jdbc.verbose=false

jdbc.printSQL=true

jdbc.idle_timeout=60

jdbc.checkout_timeout=3000

---------------------------------------------------------

其中的jdbc.password=dbpasswd需要加密处理,加密jdbc.password 的openssl命令如下:

openssl enc -aes-128-cbc -e -in in.txt -out out.txt -K 4efa4d9b4a40c3bb1334bd2a8185682d -iv 614aa06dc68709e6d2bcf879a8bfe848 -p -a

命令解释:

1、in.txt中存放数据库的明文密码;

2、使用openssl的aes-cbc加密;

3、密文输出至out.txt文件中;

4、加密密钥为4efa4d9b4a40c3bb1334bd2a8185682d;

5、初始化向量为614aa06dc68709e6d2bcf879a8bfe848。

openssl命令执行后针对out.txt文件的密文密码做base64编码,此处的输出存入jdbc.properties的jdbc.password。

在数据库连接过程中,通过判断jdbc.password 域数据base64解码是否成功,如果成功则jdbc.password中为密文密码,继续执行aes解密。

如果base64解码失败,则jdbc.password域数据为明文数据库连接密码。

流程图如下:

伪代码如下:

---------------------------------------------------------

public void setPassword(String password) {

    if (StringUtil.isEmpty(password)) {

        return;

    }

    this.password = password;//默认直接是密码

    final boolean isBase64 = Base64.isBase64(password);

    if (isBase64) { // 如果是Base64的尝试解码

        try {

            byte[] base64DecodedPwd = Base64.decodeBase64(password);

            //对称加密数据一定是16的倍数(AES128)

            boolean isBlock16 = (base64DecodedPwd.length % 16 == 0);

            //不可见字符,是加密密码

            if (isBlock16 &&

                !new String(base64DecodedPwd, "UTF-8").matches(PATTERN_COMMONS_CHARS)) {

               

                //明文密码

                String plainPwd = StringUtil.trimToEmpty(decodePassword(password));

               

                // 解密结果是可见字符

                if (plainPwd.matches(PATTERN_COMMONS_CHARS)) {

                    this.password = plainPwd;

                }

            }

        } catch (Exception e) {

            LOGGER.debug(e);

            LOGGER.info("使用明文密码: ", e.getMessage());

        }

    }

    this.connectionProperties.setProperty("password", this.password);

public static String decodePassword(String secretBase64) throws Exception {

    SecretKeySpec key = new SecretKeySpec(pwdAesRawKey, "AES");

    //使用CBC模式,需要一个向量iv,可增加加密算法的强度

    IvParameterSpec iv = new IvParameterSpec(IV);

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    cipher.init(Cipher.DECRYPT_MODE, key, iv);

    byte[] decode = cipher.doFinal(Base64.decodeBase64(secretBase64));

    return new String(decode, "UTF-8");

---------------------------------------------------------

补充说明

加解密属于cpu密集型,需要考虑防cc。

建议将加解密操作抽象成安全基础服务,针对交易量大的可以考虑集成加解密加速硬件设备。

密码学在应用过程中需要注意密钥的保护,有条件的企业可自研密钥管理系统,全生命周期的管控密钥。

不得已需要采用预置密钥的,也需要采用密钥打散、变形的方案;多少也要增加逆向的难度。

本期的分享结束,即将迎来YISRC上线一周年大庆~

各位敬请期待~

长按指纹

一键关注

点击阅读原文查看更多