密码学的一些概念


经典参考书

Practical Cryptography Niels Ferguson and Bruce Schneier, John Wiley & Sons, 2003

上面的讲的理论,下面的实践重点推荐

Network Security with OpenSSL

分组对称加密的分组模式 wiki解释加密模式

glossary

PKI:Public Key Infrastructure 支持公开密钥管理并能支持认证、加密、完整性和可追究性服务的基础设施。 认证中心CA 作为PKI 的核心部分,CA 实现了PKI 中一些很重要的功能。

X.509:由国际电信联盟(ITU-T)制定的数字证书标准。在X.509系统中,CA签发的证书依照X.500的管理,绑定了一个唯一甄别名(DN-Distinguished Name )。 X.509包含了一个证书吊销列表(CRL-Certificate Revocation List)实施的标准。

X.509扩展文件

  • cer, .crt - 通常被用于二进制的DER文件格式(同于.der), 不过也被用于Base64编码的文件 (例如 .pem).
  • P7B - 同于 .p7c
  • P7C - PKCS#7证书格式,仅仅包含证书和CRL列表信息,没有私钥。
  • PFX - 同于 .p12
  • P12 -PKCS#12文件, 包含证书(公钥)和私钥(受密码保护),已经完整的证书链信。

hash算法中加盐

加盐的目的是为了防止密码脱库后的彩虹表攻击。

为什么要在密码里加点“盐”

如何部署加盐

盐(Salt)

在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。

以上这句话是维基百科上对于 Salt 的定义,但是仅凭这句话还是很难理解什么叫 Salt,以及它究竟起到什么作用。

第一代密码

早期的软件系统或者互联网应用,数据库中设计用户表的时候,大致是这样的结构:

mysql> desc User;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)  | NO   |     |         |       |
| PassWord | varchar(150) | NO   |     |         |       |
+----------+--------------+------+-----+---------+-------+

数据存储形式如下:

mysql> select * from User;
+----------+----------+
| UserName | PassWord |
+----------+----------+
| lichao   | 123      |
| akasuna  | 456      |
+----------+----------+

主要的关键字段就是这么两个,一个是登陆时的用户名,对应的一个密码,而且那个时候的用户名是明文存储的,如果你登陆时用户名是 123,那么数据库里存的就是 123。这种设计思路非常简单,但是缺陷也非常明显,数据库一旦泄露,那么所有用户名和密码都会泄露,后果非常严重。参见 《CSDN 详解 600 万用户密码泄露始末》。

第二代密码

为了规避第一代密码设计的缺陷,聪明的人在数据库中不在存储明文密码,转而存储加密后的密码,典型的加密算法是 MD5 和 SHA1,其数据表大致是这样设计的:

mysql> desc User;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)  | NO   |     |         |       |
| PwdHash  | char(32)     | NO   |     |         |       |
+----------+--------------+------+-----+---------+-------+

数据存储形式如下:

mysql> select * from User;
+----------+----------------------------------+
| UserName | PwdHash                          |
+----------+----------------------------------+
| lichao   | 202cb962ac59075b964b07152d234b70 |
| akasuna  | 250cf8b51c773f3f8dc8b4be867a9a02 |
+----------+----------------------------------+

假如你设置的密码是 123,那么数据库中存储的就是 202cb962ac59075b964b07152d234b7040bd001563085fc35165329ea1ff5c5ecbdbbeef。当用户登陆的时候,会把用户输入的密码执行 MD5(或者 SHA1)后再和数据库就行对比,判断用户身份是否合法,这种加密算法称为散列。

严格地说,这种算法不能算是加密,因为理论上来说,它不能被解密。所以即使数据库丢失了,但是由于数据库里的密码都是密文,根本无法判断用户的原始密码,所以后果也不算太严重。

在一个使用hash的账号系统中,用户注册和认证的大致流程如下:

  1. 用户创建自己的账号
  2. 用户密码经过hash操作之后存储在数据库中。没有任何明文的密码存储在服务器的硬盘上。
  3. 用户登陆的时候,将用户输入的密码进行hash操作后与数据库里保存的密码hash值进行对比。
  4. 如果hash值完全一样,则认为用户输入的密码是正确的。否则就认为用户输入了无效的密码。
  5. 每次用户尝试登陆的时候就重复步骤3和步骤4。

第三代密码

本来第二代密码设计方法已经很不错了,只要你密码设置得稍微复杂一点,就几乎没有被破解的可能性。但是如果你的密码设置得不够复杂,被破解出来的可能性还是比较大的。

好事者收集常用的密码,然后对他们执行 MD5 或者 SHA1,然后做成一个数据量非常庞大的数据字典,然后对泄露的数据库中的密码就行对比,如果你的原始密码很不幸的被包含在这个数据字典中,那么花不了多长时间就能把你的原始密码匹配出来。这个数据字典很容易收集,CSDN 泄露的那 600w 个密码,就是很好的原始素材。

于是,第三代密码设计方法诞生,用户表中多了一个字段:

mysql> desc User;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| UserName | varchar(50) | NO   |     |         |       |
| Salt     | char(50)    | NO   |     |         |       |
| PwdHash  | char(32)    | NO   |     |         |       |
+----------+-------------+------+-----+---------+-------+

数据存储形式如下:

mysql> select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt                       | PwdHash                          |
+----------+----------------------------+----------------------------------+
| lichao   | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| akasuna  | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |
+----------+----------------------------+----------------------------------+

Salt 可以是任意字母、数字、或是字母或数字的组合,但必须是随机产生的,每个用户的 Salt 都不一样,用户注册的时候,数据库中存入的不是明文密码,也不是简单的对明文密码进行散列,而是 MD5( 明文密码 + Salt),也就是说:

MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'

当用户登陆的时候,同样用这种算法就行验证。

由于加了 Salt,即便数据库泄露了,但是由于密码都是加了 Salt 之后的散列,坏人们的数据字典已经无法直接匹配,明文密码被破解出来的概率也大大降低。

是不是加了 Salt 之后就绝对安全了呢?淡然没有!坏人们还是可以他们数据字典中的密码,加上我们泄露数据库中的 Salt,然后散列,然后再匹配。但是由于我们的 Salt 是随机产生的,假如我们的用户数据表中有 30w 条数据,数据字典中有 600w 条数据,坏人们如果想要完全覆盖的坏,他们加上 Salt 后再散列的数据字典数据量就应该是 300000* 6000000 = 1800000000000,一万八千亿啊,干坏事的成本太高了吧。但是如果只是想破解某个用户的密码的话,只需为这 600w 条数据加上 Salt,然后散列匹配。可见 Salt 虽然大大提高了安全系数,但也并非绝对安全。

实际项目中,Salt 不一定要加在最前面或最后面,也可以插在中间嘛,也可以分开插入,也可以倒序,程序设计时可以灵活调整,都可以使破解的难度指数级增长。

PS,文中所谓第一、二、三代密码的称呼,是我自己 YY 的。

opessl中秘钥的存储格式

openssl中需要使用pem和der两种格式存储秘钥,der是asn1格式编码的二进制文件,pem是der的base64编码后加上头位组成的.基础都是asn1抽象数据结构. ASN.1 wiki,该结构是通过一系列的数据类型定义和类似xml的结构化语法来描述一个复杂数据结构的规范.

如下例:

定义一个结构

FooProtocol DEFINITIONS ::= BEGIN

    FooQuestion ::= SEQUENCE {
        trackingNumber INTEGER,
        question       IA5String
    }

    FooAnswer ::= SEQUENCE {
        questionNumber INTEGER,
        answer         BOOLEAN
    }

END

实例化上面定义的结构

myQuestion FooQuestion ::= {
    trackingNumber     5,
    question           "Anybody there?"
}

INTEGER IA5String BOOLEAN均为ASN1定义的标准数据类型.

上面描述了抽象的数据结构定义,具体的存储结构如下:

30 -- 标签说明 SEQUENCE
13 -- 长度

02 -- 标签说明 INTEGER
01 -- 长度
05 -- value

16 -- 标签说明 IA5String
0e -- 长度
41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f -- value 
("Anybody there?" in ASCII)

所以最终得到的存储二进制(DER)结果为

30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f

对于ASN1数据格式的解析工具有 dumpasn1 openssl asn1parse 重点使用openssl的asn1parse功能,文档 ASN1只定义了抽象的数据结构,并没有标明具体每一位有什么意义,在密码领域该结构的含义一般由PKCS#*家族定义

具体的定义

下面用具体的例子来看下对应文件的内容.

RSA Public Key file (PKCS#1)

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,  -- n
    publicExponent    INTEGER   -- e
}

Public Key file (PKCS#8)

PublicKeyInfo ::= SEQUENCE {
  algorithm       AlgorithmIdentifier,
  PublicKey       BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm       OBJECT IDENTIFIER,
  parameters      ANY DEFINED BY algorithm OPTIONAL
}
timfeng@15:53:41:~/mycode/github/openssl $openssl rsa -in rsaprivate.pem -pubout  -out rsapublic.pem 
writing RSA key
timfeng@15:54:05:~/mycode/github/openssl $openssl asn1parse -in rsapublic.pem
    0:d=0  hl=3 l= 159 cons: SEQUENCE          
    3:d=1  hl=2 l=  13 cons: SEQUENCE          
    5:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
   16:d=2  hl=2 l=   0 prim: NULL              
   18:d=1  hl=3 l= 141 prim: BIT STRING 
timfeng@15:54:17:~/mycode/github/openssl $openssl asn1parse -in rsapublic.pem -strparse 18
    0:d=0  hl=3 l= 137 cons: SEQUENCE          
    3:d=1  hl=3 l= 129 prim: INTEGER           :C0EF71C93D758A63483C0C7995D7BF3EA5215969A5B76ACC049B0709D7194EC179ED323E7A7062B87B2F0C0D43F07029DCFAEDDD11C6FDF586938323972372145CC568A35E2EED7FE73068E4D3A550DFCBE146F4E071DC6AB067AB6B3839EDE0C8CD6D0FB3D40875E4713C644987B2872C6CE3C3C2655BB30FAA82C7E4F9BF3F
  135:d=1  hl=2 l=   3 prim: INTEGER           :010001

另一种方法

openssl rsa -pubin -inform PEM -text -noout < public.key

timfeng@15:58:09:~/mycode/github/openssl $openssl pkey -in rsaprivate.pem -text_pub -noout
Public-Key: (1024 bit)
Modulus:
    00:c0:ef:71:c9:3d:75:8a:63:48:3c:0c:79:95:d7:
    bf:3e:a5:21:59:69:a5:b7:6a:cc:04:9b:07:09:d7:
    19:4e:c1:79:ed:32:3e:7a:70:62:b8:7b:2f:0c:0d:
    43:f0:70:29:dc:fa:ed:dd:11:c6:fd:f5:86:93:83:
    23:97:23:72:14:5c:c5:68:a3:5e:2e:ed:7f:e7:30:
    68:e4:d3:a5:50:df:cb:e1:46:f4:e0:71:dc:6a:b0:
    67:ab:6b:38:39:ed:e0:c8:cd:6d:0f:b3:d4:08:75:
    e4:71:3c:64:49:87:b2:87:2c:6c:e3:c3:c2:65:5b:
    b3:0f:aa:82:c7:e4:f9:bf:3f
Exponent: 65537 (0x10001)

RSA Private Key file (PKCS#1)

RSAPrivateKey ::= SEQUENCE {
  version           Version,
  modulus           INTEGER,  -- n
  publicExponent    INTEGER,  -- e
  privateExponent   INTEGER,  -- d
  prime1            INTEGER,  -- p
  prime2            INTEGER,  -- q
  exponent1         INTEGER,  -- d mod (p-1)
  exponent2         INTEGER,  -- d mod (q-1)
  coefficient       INTEGER,  -- (inverse of q) mod p
  otherPrimeInfos   OtherPrimeInfos OPTIONAL
}
timfeng@15:47:51:~/mycode/github/openssl $openssl genrsa -out rsaprivate.pem 1024
Generating RSA private key, 1024 bit long modulus
...................................................++++++
............++++++
e is 65537 (0x10001)
timfeng@15:47:56:~/mycode/github/openssl $openssl asn1parse -in rsaprivate.pem 
    0:d=0  hl=4 l= 604 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=3 l= 129 prim: INTEGER           :C0EF71C93D758A63483C0C7995D7BF3EA5215969A5B76ACC049B0709D7194EC179ED323E7A7062B87B2F0C0D43F07029DCFAEDDD11C6FDF586938323972372145CC568A35E2EED7FE73068E4D3A550DFCBE146F4E071DC6AB067AB6B3839EDE0C8CD6D0FB3D40875E4713C644987B2872C6CE3C3C2655BB30FAA82C7E4F9BF3F
  139:d=1  hl=2 l=   3 prim: INTEGER           :010001
  144:d=1  hl=3 l= 128 prim: INTEGER           :220394C9E7BDEB31E9B03C7ACB02084361156A1008193808D3DF08F4A129630B1515CE3FD9922AB55F7241EBACC89CDDA489D099A2E2B42F2F149E900DBB40072C0DE80C8073821821340BBB66F29A9D07B987D62F9316436BC21CD98303437E6DF093D5BFD57064F7A3CB93401FB65BFEB958291BB291CB9770502FF5572371
  275:d=1  hl=2 l=  65 prim: INTEGER           :FAA1D643AEFC818B8C7729C38D118D80247F14E3594DC5DC158224D102AF6FA8EAE5091382D50F97D08D449891C8901A87720F3E6216D0CCD9D5ACEEB6BA0ECD
  342:d=1  hl=2 l=  65 prim: INTEGER           :C511449820FE84B086971AB76532A32A6CF73B6CC8630460900CD18B8B5BFF2D828E159646FE58C0D475F45C468C571EAC37B29114017E0B73629A5F982DAE3B
  409:d=1  hl=2 l=  65 prim: INTEGER           :87B565AD546CC94064988976A745DFD688EC7EC4F3B016F8AA74C3B6A0BB99BC5F24111C0D758C71E9ACF47DA0427734C62E4E6EBAF21BB1302A70EEF8E619E9
  476:d=1  hl=2 l=  64 prim: INTEGER           :6E2D9E5D33AD39407CAF44B79E73B638051E20F24C02D832A8B711FCE3D8768DDA3D582261E4D8784F10F7A313AA6916F4105BDCB303B695EE58059946BADB7B
  542:d=1  hl=2 l=  64 prim: INTEGER           :3C0E6B07A23EFCD70E3AC7855F2220BCF70941C65A51AA957E02C91244DE130775E6B59701C4CA5EFF7BEE3AABE46FC144D52F0150187D0904E41C9FFC40FC32
timfeng@15:48:04:~/mycode/github/openssl $

Private Key file (PKCS#8)

PrivateKeyInfo ::= SEQUENCE {
  version         Version,
  algorithm       AlgorithmIdentifier,
  PrivateKey      BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm       OBJECT IDENTIFIER,
  parameters      ANY DEFINED BY algorithm OPTIONAL
}

DSA PublicKey

PublicKeyInfo ::= SEQUENCE {
  algorithm AlgorithmIdentifier,
  PublicKey BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm ALGORITHM.id,
  parameters Dss-Parms
}

Dss-Parms ::= SEQUENCE {
  p INTEGER,
  q INTEGER,
  g INTEGER
}

DSA PrivateKey

PrivateKeyInfo ::= SEQUENCE {
  version Version,
  algorithm AlgorithmIdentifier,
  PrivateKey OCTETSTRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm ALGORITHM.id,
  parameters Dss-Parms
}

Dss-Parms ::= SEQUENCE {
  p INTEGER,
  q INTEGER,
  g INTEGER
}

DSAPrivateKey ::= OCTETSTRING {
  privateExponent INTEGER
}

X509

非对称加密算法需要公钥的认证以确认公钥确实是属于某个人,认证的方式形成了两种主要的方式:(public key infrastructure)[https://en.wikipedia.org/wiki/Publickeyinfrastructure] 使用集中的CA的方式认证公钥; Web of trust使用分散的方式认证公钥.S/MIME使用了第一种方式,对应的同样功能的PGP使用了后面一种.

PGP

在X509系统中,CA签发一个和public key结合的特定信息(distinguished name、e-mail address、DNS entry),


Copyright © FengGuangtu 2017