本文主要翻译自 Practical-Cryptography-for-Developers-Book,但是笔者也补充了 HMAC 的 Python 实现以及 scrypt 使用示例。

《写给开发人员的实用密码学》系列文章目录:

一、MAC 消息认证码

MAC 消息认证码,即 Message Authentication Code,是用于验证消息的一小段信息。

换句话说,能用它确认消息的真实性——消息来自指定的发件人并且没有被篡改。

MAC 值通过允许验证者(也拥有密钥)检测消息内容的任何更改来保护消息的数据完整性及其真实性。

一个安全的 MAC 函数,跟加密哈希函数非常类似,也拥有如下特性:

  • 快速:计算速度要足够快
  • 确定性:对同样的消息跟密钥,应该总是产生同样的输出
  • 难以分析:对消息或密钥的任何微小改动,都应该使输出完全发生变化
  • 不可逆:从 MAC 值逆向演算出消息跟密钥应该是不可行的。
  • 无碰撞:找到具有相同哈希的两条不同消息应该非常困难(或几乎不可能)

但是 MAC 算法比加密哈希函数多一个输入值:密钥,因此也被称为 keyed hash functions,即「加密钥的哈希函数」。

如下 Python 代码使用 key 跟 消息计算出对应的 HMAC-SHA256 值:

import hashlib, hmac, binascii

key = b"key"
msg = b"some msg" mac = hmac.new(key, msg, hashlib.sha256).digest() print(f"HMAC-SHA256({key}, {msg})", binascii.hexlify(mac).decode('utf8')) # => HMAC-SHA256(b'key', b'some msg') = 32885b49c8a1009e6d66662f8462e7dd5df769a7b725d1d546574e6d5d6e76ad

HMAC 的算法实际上非常简单,我参考 wiki/HMAC 给出的伪码,编写了下面这个 Python 实现,没几行代码,但是完全 work:

import hashlib, binascii

def xor_bytes(b1, b2):
return bytes(a ^ c for a, c in zip(b1, b2)) def my_hmac(key, msg, hash_name):
# hash => (block_size, output_size)
# 单位是 bytes,数据来源于 https://en.wikipedia.org/wiki/HMAC
hash_size_dict = {
"md5": (64, 16),
"sha1": (64, 20),
"sha224": (64, 28),
"sha256": (64, 32),
# "sha512/224": (128, 28), # 这俩算法暂时不清楚在 hashlib 里叫啥名
# "sha512/256": (128, 32),
"sha_384": (128, 48),
"sha_512": (128, 64),
"sha3_224": (144, 28),
"sha3_256": (136, 32),
"sha3_384": (104, 48),
"sha3_512": (72, 64),
}
if hash_name not in hash_size_dict:
raise ValueError("unknown hash_name") block_size, output_size = hash_size_dict[hash_name]
hash_ = getattr(hashlib, hash_name) # 确保 key 的长度为 block_size
block_sized_key = key
if len(key) > block_size:
block_sized_key = hash_(key).digest() # 用 hash 函数进行压缩
if len(key) < block_size:
block_sized_key += b'\x00' * (block_size - len(key)) # 末尾补 0 o_key_pad = xor_bytes(block_sized_key, (b"\x5c" * block_size)) # Outer padded key
i_key_pad = xor_bytes(block_sized_key, (b"\x36" * block_size)) # Inner padded key return hash_(o_key_pad + hash_(i_key_pad + msg).digest()).digest() # 下面验证下
key = b"key"
msg = b"some msg" mac_ = my_hmac(key, msg, "sha256")
print(f"HMAC-SHA256({key}, {msg})", binascii.hexlify(mac_).decode('utf8')) # 输出跟标准库完全一致:
# => HMAC-SHA256(b'key', b'some msg') = 32885b49c8a1009e6d66662f8462e7dd5df769a7b725d1d546574e6d5d6e76ad

MAC 与哈希函数、数字签名的区别

上一篇文章提到过,哈希函数只负责生成哈希值,不负责哈希值的可靠传递。

而数字签名呢,跟 MAC 非常相似,但是数字签名使用的是非对称加密系统,更复杂,计算速度也更慢。

MAC 的功能跟数字签名一致,都是验证消息的真实性(authenticity)、完整性(integrity)、不可否认性(non-repudiation),但是 MAC 使用哈希函数或者对称密码系统来做这件事情,速度要更快,算法也更简单。

MAC 的应用

1. 验证消息的真实性、完整性

这是最简单的一个应用场景,在通信双向都持有一个预共享密钥的前提下,通信时都附带上消息的 MAC 码。

接收方也使用「收到的消息+预共享密钥」计算出 MAC 码,如果跟收到的一致,就说明消息真实无误。

注意这种应用场景中,消息是不保密的!

2. AE 认证加密 - Authenticated encryption

常用的加密方法只能保证数据的保密性,并不能保证数据的完整性。

而这里介绍的 MAC 算法,或者还未介绍的基于非对称加密的数字签名,都只能保证数据的真实性、完整性,不能保证数据被安全传输。

而认证加密,就是将加密算法与 MAC 算法结合使用的一种加密方案。

在确保 MAC 码「强不可伪造」的前提下,首先对数据进行加密,然后计算密文的 MAC 码,再同时传输密文与 MAC 码,就能同时保证数据的保密性、完整性、真实性,这种方法叫 Encrypt-then-MAC, 缩写做 EtM. 接收方在解密前先计算密文的 MAC 码与收到的对比,就能验证密文的完整性与真实性。

AE 有一种更安全的变体——带有关联数据的认证加密 (authenticated encryption with associated data,AEAD)。

AEAD 将「关联数据(Associated Data, AD)」——也称为「附加验证数据(Additional Authenticated Data, AAD)」——绑定到密文和它应该出现的上下文,以便可以检测和拒绝将有效密文“剪切并粘贴”到不同上下文的尝试。 AEAD 用于加密和未加密数据一起使用的场景(例如,在加密的网络协议中),并确保整个数据流经过身份验证和完整性保护。

换句话说,AEAD 增加了检查某些内容的完整性和真实性的能力。

我们会在第六章「对称加密算法」中看到如何通过 Python 使用 AEAD 加密方案 AES-256-GCM.

3. 基于 MAC 的伪随机数生成器

MAC 码的另一个用途就是伪随机数生成函数,相比直接使用熵+哈希函数的进行伪随机数计算,MAC 码因为多引入了一个变量 key,理论上它会更安全。

这种场景下,我们称 MAC 使用的密钥为 salt,即盐。

next_seed = MAC(salt, seed)

二、KDF 密钥派生函数

我们都更喜欢使用密码来保护自己的数据而不是二进制的密钥,因为相比之下二进制密钥太难记忆了,字符形式的密码才是符合人类思维习惯的东西。

可对计算机而言就刚好相反了,现代密码学的很多算法都要求输入是一个大的数字,二进制的密钥就是这样一个大的数字。

因此显然我们需要一个将字符密码(Password)转换成密钥(Key)的函数,这就是密钥派生函数 Key Derivation Function.

直接使用 SHA256 之类的加密哈希函数来生成密钥是不安全的,因为为了方便记忆,通常密码并不会很长,绝大多数人的密码长度估计都不超过 15 位。

甚至很多人都在使用非常常见的弱密码,如 123456 admin 生日等等。

这就导致如果直接使用 SHA256 之类的算法,许多密码将很容易被暴力破解、字典攻击、彩虹表攻击等手段猜测出来!

KDF 目前主要从如下三个维度提升 hash 碰撞难度:

  1. 时间复杂度:对应 CPU/GPU 计算资源
  2. 空间复杂度:对应 Memory 内存资源
  3. 并行维度:使用无法分解的算法,锁定只允许单线程运算

主要手段是加盐,以及多次迭代。这种设计方法被称为「密钥拉伸 Key stretching」。

因为它的独特属性,KDF 也被称作慢哈希算法。

目前比较著名的 KDF 算法主要有如下几个:

  1. PBKDF2:这是一个非常简单的加密 KDF 算法,目前已经不推荐使用。
  2. Bcrypt:安全性在下降,用得越来越少了。不建议使用。
  3. Scrypt:可以灵活地设定使用的内存大小,在 argon2 不可用时,可使用它。
  4. Argon2:目前最强的密码 Hash 算法,在 2015 年赢得了密码 Hash 竞赛。

如果你正在开发一个新的程序,需要使用到 KDF,建议选用 argon2/scrypt.

Python 中最流行的加密库是 cryptographyrequests/flask 底层就使用了它,下面我们使用这个库来演示下 Scrypt 算法的使用:

# pip install cryptography==36.0.1
import os
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt salt = os.urandom(16) # derive
kdf = Scrypt(
salt=salt,
length=32,
n=2**14,
r=8,
p=1,
)
key = kdf.derive(b"my great password") # verify
kdf = Scrypt(
salt=salt,
length=32,
n=2**14,
r=8,
p=1,
)
kdf.verify(b"my great password", key)

参考

写给开发人员的实用密码学(三)—— MAC 与密钥派生函数 KDF的更多相关文章

  1. 写给开发人员的实用密码学(七)—— 非对称密钥加密算法 RSA/ECC

    本文部分内容翻译自 Practical-Cryptography-for-Developers-Book,笔者补充了密码学历史以及 openssl 命令示例,并重写了 RSA/ECC 算法原理.代码示 ...

  2. 成为一个高效的web开发人员,只需要三步

    想成为一名专业的web开发人员并不像你想象的那么容易,开发人员在开发自己的web项目时常常需要牢记很多东西,他们要不断寻找新理念,新创意,在特定时间内开发出高质量的产品,一名优秀的程序员必须明白时间的 ...

  3. Qt开发中的实用笔记三--关于各种类的零碎知识点:

    1,QUuid()创建唯一标识码,在创建数据库实体ID和链接数据库QSqlDatabase时非常方便 2,QScrollArea与QScrollBar,如果是要在widget中添加窗口滑动QScrol ...

  4. EXT.NET高效开发(三)——使用Chrome浏览器的开发人员工具

    这篇帖子老少皆宜,不分男女,不分种族,不分职业.俗话说:“磨刀不误砍柴工”.掌握一些开发工具的使用,对自己帮助是很大的(无论是用于分析问题,还是提高生产力).本篇就讲述如何利用Chrome浏览器(这里 ...

  5. [搬运] 写给 C# 开发人员的函数式编程

    原文地址:http://www.dotnetcurry.com/csharp/1384/functional-programming-fsharp-for-csharp-developers 摘要:作 ...

  6. Web 开发人员和设计师必读文章推荐【系列三十】

    <Web 前端开发精华文章推荐>2014年第9期(总第30期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  7. 每位iOS开发人员不容错过的10大实用工具

    内容简介 1.iOS简介 2.iOS开发十大实用工具之开发环境 3.iOS开发十大实用工具之图标设计 4.iOS开发十大实用工具之原型设计 5.iOS开发十大实用工具之演示工具 6.iOS开发十大实用 ...

  8. 写给Android App开发人员看的Android底层知识(1)

    这个系列的文章一共8篇,我酝酿了很多年,参考了很多资源,查看了很多源码,直到今天把它写出来,也是战战兢兢,生怕什么地方写错了,贻笑大方. (一)引言 早在我还是Android菜鸟的时候,有很多技术我都 ...

  9. 写给Android App开发人员看的Android底层知识(3)

    (七)App启动流程第2篇 书接上文,App启动一共有七个阶段,上篇文章篇幅所限,我们只看了第一阶段,接下来讲剩余的六个阶段,仍然是拿斗鱼App举例子. 简单回顾一下第一阶段的流程,就是Launche ...

随机推荐

  1. 最近公共祖先-LCA

    题目描述 时间限制:1.2s 内存限制:256.0MB 问题描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入格式 第一行包含三个正整数\(N\),\(M\),\(S\),分别 ...

  2. Linux安装MySQL详细步骤(CentOS6、CentOS7)

    1.查看mysql的依赖(centos7 要把mysql改成mariadb) rpm -qa | grep mysql 2.删除mysql的依赖,可以两个都执行(centos7 要把mysql改成ma ...

  3. HTML元素的隐藏方式

    感谢原文作者:幼儿园中的小小白 原文链接:https://blog.csdn.net/weixin_43846130/article/details/95963426 一.元素的隐藏方式: 1.dis ...

  4. B/S与C/S

    B/S结构(Browser/Server,浏览器/服务器模式),是web兴起后的一种网络结构模式,web浏览器是客户端最主要的应用软件.这种模式统一了客户端,将系统功能实现的核心部分集中到服务器上,简 ...

  5. 通过bindservice方式调用服务方法里面的过程

    为什么要引入bindService:目的为了调用服务里面的方法 (1)定义一个服务 服务里面有一个方法需要Activity调用 (2)定义一个中间人对象(IBinder) 继承Binder (3)在o ...

  6. Mysql数据库优化技术之配置篇、索引篇 ( 必看 必看 转)

    转自:Mysql数据库优化技术之配置篇.索引篇 ( 必看 必看 ) (一)减少数据库访问对于可以静态化的页面,尽可能静态化对一个动态页面中可以静态的局部,采用静态化部分数据可以生成XML,或者文本文件 ...

  7. MySQL 数据库SQL语句——高阶版本1

    MySQL 数据库SQL语句--高阶版本 实验准备,数据表配置 mysql -uroot -p show databases; create database train_ticket; use tr ...

  8. postman项目接口文档和登录步骤原理

    培训内容 实训项目:非常果岭-发现模块接口测试,单接口.流程脚本编写: 使用工具:postman 培训方式 1)postman使用说明 2)项目接口文档和登录步骤原理   一.首先了解postman使 ...

  9. 帆软报表(finereport)雷达图钻取详细点新页面展示

    添加参数栏,季度下拉框的空间名为combobox0 添加雷达图,通过第三页面做跳转 雷达图钻取.cpt为联动钻取的第三页面 添加纬度(所点击钻取的点) 参数   wd 添加季度参数 jd    值为季 ...

  10. 学习Spring5必知必会(1)~未使用spring前的麻烦

    一.未使用spring前的麻烦 开闭原则:扩展是开放的,但是对于修改是"封闭的". 1.代码耦合度比较高[不符合开闭原则]: public class EmployeeServic ...