moooofly / MarkSomethingDownLLS

本仓库用于记录自 2017年10月16日起,在英语流利说任职期间的各方面知识汇总(以下内容不足以体现全部,一些敏感内容已移除)~
MIT License
72 stars 37 forks source link

AES 加密 #54

Open moooofly opened 5 years ago

moooofly commented 5 years ago

AES 加密

简史

confidentiality mode

The earliest modes of operation, ECB, CBC, OFB, and CFB, date back to 1981 and were specified in FIPS 81, DES Modes of Operation.

ECBCBCOFBCFB 这四种是属于 DES 时代就存在的操作模式;

In 2001, the US National Institute of Standards and Technology (NIST) revised its list of approved modes of operation by including AES as a block cipher and adding CTR mode in SP800-38A.

后来,随着 AES 也加入到 block cipher 中,又增加了 CTR 这种操作模式;

Finally, in January, 2010, NIST added XTS-AES in SP800-38E.

再后来,又增加了 XTS-AES 操作模式;

authenticity mode

The block cipher modes ECB, CBC, OFB, CFB, CTR, and XTS provide confidentiality, but they do not protect against accidental modification or malicious tampering.

操作模式提供的是保密功能,但无法针对意外修改或恶意篡改提供保护;

Modification or tampering can be detected with a separate message authentication code such as CBC-MAC, or a digital signature. The cryptographic community recognized the need for dedicated integrity assurances and NIST responded with HMAC, CMAC, and GMAC.

这种需要通过 MAC 或 digital signature 数字签名进行保护;

HMAC was approved in 2002 as FIPS 198, The Keyed-Hash Message Authentication Code (HMAC), CMAC was released in 2005 under SP800-38B, Recommendation for Block Cipher Modes of Operation: The CMAC Mode for Authentication, and GMAC was formalized in 2007 under SP800-38D, Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC.

Authenticated encryption

The cryptographic community observed that compositing (combining) a confidentiality mode with an authenticity mode could be difficult and error prone. They therefore began to supply modes which combined confidentiality and data integrity into a single cryptographic primitive (an encryption algorithm). These combined modes are referred to as authenticated encryption, AE or "authenc". Examples of AE modes are CCM (SP800-38C), GCM (SP800-38D), CWC, EAX, IAPM, and OCB.

社区发现单纯的将一种 confidentiality mode 和一种 authenticity mode 组合在一起是有一定困难的,并且容易出错;因此,社区开始研究如何将两者合二为一,这种 combined modes 被称作 authenticated encryption ;

authenticated encryption = confidentiality mode + authenticity mode

AE mode 的代表有:

参考:


block cipher 有五种操作模式:

下文中使用的符号和对应含义

符号 含义
Ek 使用秘钥k对输入做对称加密运算
XOR 异或运算
Mh 将输入与秘钥h在有限域GF(2^128)上做乘法

参考:

推荐

示例代码

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "errors"
    "fmt"
    "io"
    "strings"
)

func addBase64Padding(value string) string {
    m := len(value) % 4
    if m != 0 {
        value += strings.Repeat("=", 4-m)
    }

    return value
}

func removeBase64Padding(value string) string {
    return strings.Replace(value, "=", "", -1)
}

func Pad(src []byte) []byte {
    padding := aes.BlockSize - len(src)%aes.BlockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(src, padtext...)
}

func Unpad(src []byte) ([]byte, error) {
    length := len(src)
    unpadding := int(src[length-1])

    if unpadding > length {
        return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
    }

    return src[:(length - unpadding)], nil
}

func encrypt(key []byte, text string) (string, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }

    msg := Pad([]byte(text))
    ciphertext := make([]byte, aes.BlockSize+len(msg))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", err
    }

    cfb := cipher.NewCFBEncrypter(block, iv)
    cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
    finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext))
    return finalMsg, nil
}

func decrypt(key []byte, text string) (string, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }

    decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text))
    if err != nil {
        return "", err
    }

    if (len(decodedMsg) % aes.BlockSize) != 0 {
        return "", errors.New("blocksize must be multipe of decoded message length")
    }

    iv := decodedMsg[:aes.BlockSize]
    msg := decodedMsg[aes.BlockSize:]

    cfb := cipher.NewCFBDecrypter(block, iv)
    cfb.XORKeyStream(msg, msg)

    unpadMsg, err := Unpad(msg)
    if err != nil {
        return "", err
    }

    return string(unpadMsg), nil
}

func main() {
    key := []byte("LKHlhb899Y09olUi")
    encryptMsg, _ := encrypt(key, "Hello World")
    fmt.Println(encryptMsg) 
    msg, _ := decrypt(key, encryptMsg)
    fmt.Println(msg) // Hello World
}
package main

import (
    "fmt"
    "crypto/aes"
    "crypto/cipher"
)

// NOTE: we choose it, no special reason
// AES key, either 16 or 32 bytes to select AES-128 or AES-256.
var key []byte = []byte("5e2aef5769b4241d")
var nonce []byte = []byte("9123ace458f2")

var userID string = "NGQ0MWQwMDAwMDAwMDkzNQ=="
var loginID string = "1826464754"

func encrypt() ([]byte) {
    // create a AES key
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err.Error())
    }

    // use the Galois Counter Mode (GCM) 128-bit, block cipher with a standard nonce
    aesgcm, err := cipher.NewGCM(block)
    if err != nil {
        panic(err.Error())
    }

    plaintext := []byte(userID+loginID)

    // encrypt
    ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)
    fmt.Printf("[AES encrypt] plaintext -> ciphertext ==> %s -> %x\n",
        string(plaintext), ciphertext)
    return ciphertext
}

func decrypt(ciphertext []byte) ([]byte) {
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err.Error())
    }

    aesgcm, err := cipher.NewGCM(block)
    if err != nil {
        panic(err.Error())
    }

    plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        panic(err.Error())
    }
    fmt.Printf("[AES decrypt] ciphertext -> plaintext ==> %x -> %s\n",
        ciphertext, string(plaintext))
    return plaintext
}

func main() {
    decrypt(encrypt())
}
moooofly commented 5 years ago

ECB(Electronic Mode 电子密码本模式)

当我们有一段明文,需要对其进行 AES 加密时,需要对明文进行分组,分组长度可为 128,256 或 512bits。采用 ECB 模式的分组密码算法加密过程如下图:

image

由上图可以看出,明文中重复的排列会反映在密文中。并且,当密文被篡改时,解密后对应的明文分组也会出错,且解密者察觉不到密文被篡改了。也就是说,ECB 不能提供对密文的完整性校验。因此,在任何情况下都不推荐使用 ECB 模式

moooofly commented 5 years ago

CTR(CounTeR 计数器模式)

在计数器模式下,我们不再对明文进行加密,而是对一个逐次累加的计数器进行加密,用加密后的比特序列与明文分组进行 XOR 得到密文。过程如下图:

image

计数器模式下,每次与明文分组进行 XOR 的比特序列是不同的,因此,计数器模式解决了 ECB 模式中,相同的明文会得到相同的密文的问题。CBC,CFB,OFB 模式都能解决这个问题;

但 CTR 的另两个优点是:

CTR 仍然不能提供密文消息完整性校验的功能

有的人可能会想到,如果将密文的 hash 值随密文一起发送,密文接收者对收到的密文计算 hash 值,与收到的 hash 值进行比对,这样是否就能校验消息的完整性呢?

再仔细想想,就能发现这其中的漏洞。当篡改者截获原始的密文消息时,先篡改密文,而后计算篡改后的密文 hash,替换掉原始消息中的密文 hash 。这样,消息接收者仍然没有办法发现对源密文的篡改。可见,使用单向散列函数计算 hash 值仍然不能解决消息完整性校验的问题。

moooofly commented 5 years ago

MAC(Message Authentication Code 消息验证码)

想要校验消息的完整性,必须引入另一个概念:消息验证码。消息验证码是一种与秘钥相关的单项散列函数。

image

密文的收发双发需要提前共享一个秘钥。密文发送者将密文的 MAC 值随密文一起发送,密文接收者通过共享秘钥计算收到密文的 MAC 值,这样就可以对收到的密文做完整性校验。当篡改者篡改密文后,没有共享秘钥,就无法计算出篡改后的密文的 MAC 值。

如果生成密文的加密模式是 CTR ,或者是其他有初始 IV 的加密模式,别忘了将初始的计数器(值)或初始向量的值作为附加消息与密文一起计算 MAC 。

moooofly commented 5 years ago

GMAC(Galois message authentication code mode 伽罗瓦消息验证码)

对应到上图中的消息认证码,GMAC 就是利用伽罗华域(Galois Field,GF,有限域)乘法运算来计算消息的 MAC 值。假设秘钥长度为 128bits,当密文大于 128bits 时,需要将密文按 128bits 进行分组。应用流程如下图:

image

moooofly commented 5 years ago

GCM(Galois/Counter Mode)

GCM 中的 G 就是指 GMAC,C 就是指 CTR 。

GCM 可以提供对消息的加密和完整性校验,另外,它还可以提供附加消息的完整性校验。在实际应用场景中,有些信息是我们不需要保密,但信息的接收者需要确认它的真实性的,例如源IP,源端口,目的IP,IV,等等。因此,我们可以将这一部分作为附加消息加入到 MAC 值的计算当中。下图的 Ek 表示用对称秘钥 k 对输入做 AES 运算。最后,密文接收者会收到密文、IV(计数器 CTR 的初始值)、MAC 值。

image

moooofly commented 5 years ago

Android cryptography

ref: https://developer.android.com/guide/topics/security/cryptography

image