密码学工程实践

487 阅读11分钟

平日的开发中,经常会遇到一些有关安全方面的概念与术语而感到困惑。

现在把我最近整理积累的一些材料经过整理记录在此,后续有关密码学与安全方面的内容也都会持续添加。

注意:

1、本文档不包含数学方面的内容和知识

2、尝试简单的解释相关术语,并不会深入解释算法

3、基于个人理解,不保证完全准确

对称加密


对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。 对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。

对称加密的一大缺点是密钥的管理与分配,换句话说,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传送给需要它的人。

DES/3DES

DES算法为密码体制中的对称密码体制,又被称为美国数据加密标准,现已被社区破解,被证明不安全,慢慢被废弃。

算法特点:分组比较短、密钥太短、密码生命周期短、运算速度较慢。

AES

AES在密码学中又称Rijndael加密法,这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用,已然成为对称密钥加密中最流行的算法之一。

  • 足够安全,能很好的抵抗差分分析和线性分析
  • 运算速度快,易于实现,而且适合用硬件电路实现
  • 对内存要求低
  • 密码长度和迭代次数可以扩展

AES难点是参数的多样化。异构系统使用aes进行通信,必须首先确保下面的五个参数是一模一样的。

快应用cipher模块支持的aes算法

  1. key length(密钥位数,密码长度)AES128,AES192,AES256(128 位、192 位或 256 位)128位对应的是16个字节,所以部分平台库上,会使用16个字符或者长度为16的字符串来做密码。

  2. key (密钥,密码)key指的就是密码了,AES128就是128位的,如果位数不够,某些库可能会自动填充到128。

  3. IV(向量)IV称为初始向量,不同的IV加密后的字符串是不同的,加密和解密需要相同的IV。

  4. mode (加密模式)AES分为几种模式,比如ECB,CBC,CFB等等,这些模式除了ECB由于没有使用IV而不太安全,其他模式差别并没有太明显。

  5. padding (填充方式)对于加密解密两端需要使用同一的PADDING模式,大部分PADDING模式为PKCS5, PKCS7, NOPADDING。

来源:blog.csdn.net/u012295261/…

node.js - aes

const crypto = require('crypto');
/**
 * AES加密的配置 
 * 1.密钥 
 * 2.偏移向量 
 * 3.算法模式CBC 
 * 4.补全值
 */
var AES_conf = {
    key: getSecretKey(), //密钥
    iv: '1012132405963708', //偏移向量
    padding: 'PKCS7Padding' //补全值
}
/**
 * 读取密钥key
 * 更具当前客户端的版本vid、平台platform获取对应的key
 */
function getSecretKey() {
    return "abcdabcdabcdabcd";
}
/**
 * AES_128_CBC 加密 
 * 128位 
 * return base64
 */
function encryption(data) {
    let key = AES_conf.key;
    let iv = AES_conf.iv;
    var cipherChunks = [];
    var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
    cipher.setAutoPadding(true);
    cipherChunks.push(cipher.update(data, 'utf8', 'base64'));
    cipherChunks.push(cipher.final('base64'));
    return cipherChunks.join('');
}
/**
 * 解密
 * return utf8
 */
function decryption(data) {
    let key = AES_conf.key;
    let iv = AES_conf.iv;
    // let padding = AES_conf.padding;

    var cipherChunks = [];
    var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
    decipher.setAutoPadding(true);
    cipherChunks.push(decipher.update(data, 'base64', 'utf8'));
    cipherChunks.push(decipher.final('utf8'));
    return cipherChunks.join('');
}

console.log(encryption('hello')); // HACyv/j3Q6wUHFF8uRmWWw==
console.log(decryption('HACyv/j3Q6wUHFF8uRmWWw==')); // hello

非对称加密


非对称加密需要两个密钥:公钥 (publickey) 和私钥 (privatekey)。公钥和私钥是一对,如果用公钥对数据加密,那么只能用对应的私钥解密。如果用私钥对数据加密,只能用对应的公钥进行解密。因为加密和解密用的是不同的密钥,所以称为非对称加密。

非对称加密算法的保密性好,它消除了最终用户交换密钥的需要,但是加解密速度要远远慢于对称加密。

RSA算法

RSA 是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。

快应用支持RSA加解密

util.js

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

function generateKeys() {
    const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
        modulusLength: 4096,
        publicKeyEncoding: {
            type: 'pkcs1',
            format: 'pem',
        },
        privateKeyEncoding: {
            type: 'pkcs1',
            format: 'pem',
            cipher: 'aes-256-cbc',
            passphrase: '',
        },
    });
    fs.writeFileSync('private.cer', privateKey);
    fs.writeFileSync('public.cer', publicKey);
}

function encrypt(plain, pathToPublicKey) {
    const publicKey = fs.readFileSync(path.resolve(__dirname, pathToPublicKey), 'utf8');
    const buffer = Buffer.from(plain, 'utf8');
    return crypto.publicEncrypt(publicKey, buffer).toString('base64');
}

function decrypt(cipher, pathToPrivateKey) {
    const privateKey = fs.readFileSync(path.resolve(__dirname, pathToPrivateKey), 'utf8');
    const buffer = Buffer.from(cipher, 'base64');
    const plain = crypto.privateDecrypt({
        key: privateKey.toString(),
        passphrase: ''
    }, buffer);
    return plain.toString('utf8')
}

index.js

const { generateKeys, encrypt, decrypt } = require('./utils');

generateKeys();
const cypher = encrypt('hello', 'public.cer');
console.log(cypher);
const plain = decrypt(cypher, 'private.cer');
console.log(plain);

/**
 wW+/d+lpaT2H0jW2v9EQZC1gINrhbMKyPR5+zwm08VLukFOpL3OPQXVdLGT1fEA3A/oYKrAZQYeZJ0gv9H87ZBf/aAVwP3R3qA5I+mQ3EkDxBHqyOg8uUK9zzttv2/cuf5Evm7dSYIIykchmNhIih9BBC5aCBCRrRSte0FTq2zvpaEITGSlEsJsBjfqwZ8yhCfR/jTEizgvE39TF2PfBLkzxa5roenjaBj6znm01lxMAdGoI5uPEPLKh06VpjAYfCkWhBRAYzxEsDbusbmsaXKGqFnQbsO8MVZkN8k1tkn4Kfum0DyhgzEtHD1vgujXZ3E4EVn/M0ehCb70G/t099w2rLWwmKHJUztGPvNrGCDCnuwAh9L/uMmN2sQAp0KHAEKGW3ikxq0U+G7Fsm7tProaa7uPyjgbAppeQu73lDGy/8QTBqAPmmpwWKMqzpnIoAwF+QxI4L7o8U9lqc2VvK5fra2nR7T4+O9SkgY8pMQqyE7lbXLujY/cZPjnb9G9n39TYMCFr8pVtqBIF/Go68QDjjkcWFGyI8uwmzA7tax6E92QWc6ETc3ACW4uxN1B+yBFsEITaYYxe57rWUMVNQC0FUSeliwaGMIeTI7S/Kl9DunY8d9XKQ7KwjEdlxkY8hFBjMBAdG/8N/ndLtjRn+wNjIgU19o/5dBWbaTaRJbM=
 hello
 * 
 * 

DSA算法

DSA(Digital Signature Algorithm,数字签名算法,用作数字签名标准的一部分),它是另一种公开密钥算法,它不能用作加密,只用作数字签名。简单的说,这是一种更高级的验证方式,用作数字签名。不单单只有公钥、私钥,还有数字签名。私钥加密生成数字签名,公钥验证数据及签名,如果数据和签名不匹配则认为验证失败。数字签名的作用就是校验数据在传输过程中不被修改,数字签名,是单向加密的升级。

ECC算法

椭圆加密算法(ECC)是一种公钥加密算法,最初由 Koblitz 和 Miller 两人于1985年提出,其数学基础是利用椭圆曲线上的有理点构成 Abel 加法群上椭圆离散对数的计算困难性。

公钥密码体制根据其所依据的难题一般分为三类:大整数分解问题类离散对数问题类椭圆曲线类。有时也把椭圆曲线类归为离散对数类。

ECC 被广泛认为是在给定密钥长度的情况下,最强大的非对称算法,因此在对带宽要求十分紧的连接中会十分有用。

不过一个缺点是加密和解密操作的实现比其他机制花费的时间长。

比特币钱包公钥的生成使用了椭圆曲线算法,通过椭圆曲线乘法可以从私钥计算得到公钥, 这是不可逆转的过程。

DH算法

全称为"Diffie-Hellman",Diffie-Hellman密钥协商(交换)算法主要解决在公网(不安全网络)秘钥配送问题。

(1)Alice与Bob确定两个大素数n和g,这两个数不用保密

(2)Alice选择另一个大随机数x,并计算A如下:A=gxmod n

(3)Alice将A发给Bob

(4)Bob  选择另一个大随机数y,并计算B如下:B=gymod n

(5)Bob将B发给Alice

(6)计算秘密密钥K1如下:K1=Bxmod n

(7)计算秘密密钥K2如下:K2=Aymod n

 K1=K2,因此Alice和Bob可以用其进行加解密

攻击者知道p和g,并且截获了Ka和Kb,但是当它们都是非常大的数的时候,依靠这四个数来计算a和b非常困难,这就是离散对数数学难题。

密钥一般都是前后端同时持有,线下交换,以规避中间人获取作恶。但是客户端保存密钥又是不安全的,所以需要一个动态/线上交换的密钥算法。

DH算法的特点是动态的,所以不需要保存到客户端,因为生成特性所以不会被中间人获取,所以是安全的。

然而,该技术也存在许多不足:

  • 没有提供双方身份的任何信息
  • 它是计算密集型的,因此容易遭受阻塞性攻击。

认证

哈希函数

在记录的关键字与记录的存储地址之间建立的一种对应关系叫哈希函数。 哈希函数就是一种映射,是从关键字到存储地址的映射。 通常,包含哈希函数的算法的算法复杂度都假设为 O(1),这就是为什么在哈希表中搜索数据的时间复杂度会被认为是"平均为 O(1) 的复杂度"。

node.js支持的哈希算法

const crypto = require('crypto');
console.log(crypto.getHashes());
[ 'RSA-MD4',
  'RSA-MD5',
  'RSA-MDC2',
  'RSA-RIPEMD160',
  'RSA-SHA1',
  'RSA-SHA1-2',
  'RSA-SHA224',
  'RSA-SHA256',
  'RSA-SHA3-224',
  'RSA-SHA3-256',
  'RSA-SHA3-384',
  'RSA-SHA3-512',
  'RSA-SHA384',
  'RSA-SHA512',
  'RSA-SHA512/224',
  'RSA-SHA512/256',
  'RSA-SM3',
  'blake2b512',
  'blake2s256',
  'id-rsassa-pkcs1-v1_5-with-sha3-224',
  'id-rsassa-pkcs1-v1_5-with-sha3-256',
  'id-rsassa-pkcs1-v1_5-with-sha3-384',
  'id-rsassa-pkcs1-v1_5-with-sha3-512',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5-sha1',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha3-224',
  'sha3-256',
  'sha3-384',
  'sha3-512',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512-224',
  'sha512-224WithRSAEncryption',
  'sha512-256',
  'sha512-256WithRSAEncryption',
  'sha512WithRSAEncryption',
  'shake128',
  'shake256',
  'sm3',
  'sm3WithRSAEncryption',
  'ssl3-md5',
  'ssl3-sha1',

MD5

MD5 即 Message-Digest Algorithm 5(信息-摘要算法 5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一,主流编程语言普遍已有 MD5 实现。 MD5 是输入不定长度信息,输出固定长度 128-bits 的算法。(但是由于128位的标准存储起来也较为麻烦,通常只截取32位)

const crypto = require('crypto')

const md5hash = crypto.createHash('md5')
    .update('Hello, world!')
    .digest('hex');
console.log(md5hash);
// 6cd3556deb0da54bca060b4c39479839

基于目前的技术,在数个小时内就可以找到MD5碰撞,使MD5算法不再适合当前的安全环境(密码学意义)。目前,MD5计算广泛应用于错误检查。

例如在一些BitTorrent下载中,软件通过计算MD5和检验下载到的碎片的完整性。

SHA

安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法,被认为是替代MD5的下一代杂凑算法。

SHA-1, SHA-224, SHA-256, SHA-384 和 SHA-512等多个变种,本质上是一样的算法,不一样的初始值。 SHA-224、SHA-256、SHA-384,和 SHA-512 并称为 SHA-2。

由于对 MD5 出现成功的破解,以及对 SHA-0 和 SHA-1 出现理论上破解的方法,需要一个与之前算法不同的,可替换的加密杂凑算法,也就是现在的 SHA-3。

安全性:SHA-1 < SHA2 < SHA3,效率反之。

如何破解哈希算法

  • 暴力破解
  • 字典攻击(庞大的字典数据库)

加盐

对于每一个用户的每一个密码,盐值都应该是独一无二且足够长。 数据库应该同时存储哈希和盐值;

存储密码的步骤

1、使用工具生成一个长度足够的盐值 2、将盐值混入密码,并使用标准的加密哈希函数进行加密,如SHA256 3、把哈希值和盐值一块儿存入数据库中对应此用户的那条记录

校验密码的步骤

1、从数据库取出用户的密码哈希值和对应盐值 2、将盐值混入用户输入的密码,而且使用一样的哈希函数进行加密 3、比较上一步的结果和数据库储存的哈希值是否相同,若是相同那么密码正确,反之密码错误

// 加盐
const password = 'Hello, world!';
const salt = Math.random().toString(36).slice(-8);
const md5hash = crypto.createHash('md5')
    .update('Hello, world!' + salt)
    .digest('hex');
console.log(md5hash);

消息认证码

MAC 消息认证码是一种确认完整性并进行认证的技术,简称MAC。

输入是一个任意长度的消息和一个两端共享的密钥,输出是一个固定长度的MAC值。

HMAC 消息认证码的一种最常见实现。H的意思是hash。

HMAC极极极简化版:

密钥绝对不能丢失,一般存储在专用服务器上。

const secret = 'abcdefg';
const shahash = crypto.createHmac('sha256', secret)
    .update('I love cupcakes')
    .digest('hex');
console.log(shahash);
// c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e

解决的问题

消息认证码可以断定发送者发出的消息与接受者接收到的信息是一致的。

无法向第三方证明,也无法防止否认(即使第三方拥有密钥,也没办法确定发送方)。

数字签名与验签

公钥与私钥总是成对儿存在,公钥是公开的,私钥是保密的。

目的:确认消息到底是谁写的?

数字签名的流程

虽然可以直接对消息进行加密,但是一般应该是对消息的散列值进行加密。

  1. 发送者生成私钥和公钥,将公钥发送给接收者
  2. 发送者将消息生成散列值,针对散列值用私钥进行加密,并将加密后的结果发送给接收者
  3. 接收者接收到消息后,生成散列值
  4. 接收到加密后的散列值后,使用公钥进行解密得到散列值
  5. 接收者将自己生成的散列值 和 解密后的散列值进行比对,如果相同则验证成功。

中间人攻击

中间人攻击的实质是截获了接收方发出的公钥或者私钥,并将自己的公钥或者私钥发送给另外一方。所以针对公钥密码的中间人攻击,针对数字签名同样有效。 具体解决的办法就是涉及公钥密码的软件都显示公钥的散列值,也就是指纹。比对指纹的正确性就能确认公钥是不是对方的公钥了(多一层保险而已,指纹也有可能被劫持)。

利用数字签名攻击公钥密码

A与B进行通信,A是用B的公钥进行加密的,B用私钥进行解密。

数字签名过程中,公钥却是用来解密的,私钥是用来加密的。

而且 公钥密码中的私钥解密 和 数字签名中的私钥加密是相同的公式。

所以,攻击者可以保存A用公钥加密后的消息。然后发邮件给B说需要做一个数字签名实验,需要B对消息进行加密。

B一旦使用自己的私钥对所谓的签名消息进行加密,实际上就是对A发送的消息进行解密。这就完成了攻击。

证书

数字证书是由权威的CA(Certificate Authority)机构给服务端进行颁发,CA机构通过服务端提供的相关信息生成证书,证书内容包含了持有人的相关信息,服务器的公钥,签署者签名信息(数字签名)等,最重要的是公钥在数字证书中。

当客户端发起请求时,服务器将该数字证书发送给客户端,客户端通过CA机构提供的公钥对加密密文进行解密获得散列值(数字签名),同时将证书内容使用相同的散列算法进行Hash得到另一个散列值,比对两个散列值,如果两者相等则说明证书没问题。

浏览器与证书

安全证书

证书细节

不受信证书

HTTPS

HTTP是不安全的 HTTP 传输面临的风险有

(1) 窃听风险:黑客可以获知通信内容。

(2) 篡改风险:黑客可以修改通信内容。

(3) 冒充风险:黑客可以冒充他人身份参与通信。

HTTPS通信原理