【密码学】一文读懂GCM(Golais计数器模式)

简介: GCM(Galois计数器模式)同样的也是NIST提出来的,这个模式基于并行化设计,下面来一起看一下这个模式具体是如何工作的吧。

一文读懂GCM(Galois/计数器模式)


3SU52(GL6E(`]Y_]4J)HMUM.jpg一文读懂GCM5CMR{%FXF_LM{14E`0STGMJ.pngGCM模式

GCM(Galois计数器模式)同样的也是NIST提出来的,这个模式基于并行化设计,下面来一起看一下这个模式具体是如何工作的吧。


预备知识

不同于CCM模式,这个模式没有用到之前学过的CTR或者MAC, 而是采用了两个新的函数:

  • 「GHASH」: 带密钥的哈希函数
  • 「GCTR」: 这个实际上也是每次加一的一个计数器,不过这个计数器有一点点小特别,具体哪里特别将在后文进行介绍

GHASH

先来看一下GHASH函数,这个函数以密钥和消息X作为输入,输出是一个128位的MAC值。

NRZ166~ACML[IOLTP~$~}GS.png

image.gifGHASH

这里的乘法和加法是基于这一个有限域之下的, 有关有限域的具体知识, 同样的不在这里赘述了, 具体过程如下:

  1. 初始化
  2. 对于每一个分组, 都有, 其中H是密钥
  3. 最后一个分组, 即为最终的MAC值

对于上面的乘法运算,参考文献给出了具体的算法。

GCTR

接下来,来看一下GCTR的具体过程,对于GCTR来说,和普通的CTR有一点点小差别,主要差别在计数器上,这个是最后32位做mod 的加法运算,前面的不变。

PZ90H%F(S3GJD2ICHRVF9ED.pngGCTR

具体规则如下:

  • 如果X是空串,直接返回
  • 对消息X进行分组,按照16个字节也就是128bit进行分组
  • 初始化
  • 对于i = 2 到 n,  = , 这里的是最右侧的32位+1并且mod
  • 如果消息长度恰好是分组的整数倍,则计算
  • 否则将截取到最后剩余的消息长度
  • C =  || ... ||


GCM过程

到这里,整个GCM的基本算法就介绍完了,下面来看一下整个GCM的过程吧。

SZ}%ON9XYUWIPL`S%X3{7`G.pngGCM过程

  1. H = E(K, ), 这里的H也就是GHASH的密钥
  2. 对于有如下两种情况
  • = 96, 注意这里是比特长度,如果是字节,那么这里是12, = IV ||  || 1
  • , 令 s = , 并且
  1. 令C = GCTR(K, (), P)
  2. 令  , 并令
  3. 新增一个有关无需加密数据和其长度的分组S, 并计算GHASH


  1. 计算T = , 我代码实现这里同样偷懒了一下,长度我直接输出了整个分组,注意一下


代码实现

老规矩,接着用rust来写啦,嘻嘻

use aes::AES;
pub struct GCM {
    aes: AES,
}
// R = 11100001 || 0(120)
const R: u128 = 0b11100001 << 120;
fn gmul_128(x: u128, y: u128) -> u128 {
    let mut z = 0u128;
    let mut v = y;
    for i in (0..128).rev() {
        let xi = (x >> i) & 1;
        if xi != 0 {
            z ^= v;
        }
        v = match v & 1 == 0 {
            true => { v >> 1 }
            false => { (v >> 1) ^ R }
        };
    }
    z
}
fn u8to128(bytes: &[u8]) -> u128 {
    let result = bytes.iter().rev().enumerate().fold(0, |acc, (i, &byte)| {
        acc | (byte as u128) << (i * 8)
    });
    result
}
fn msb_s(s: usize, bytes: [u8; 16]) -> Vec<u8> {
    let mut result = vec![];
    let n = s / 8;
    let remain = s % 8;
    for i in 0..n {
        result.push(bytes[i]);
    }
    if remain > 0 {
        result.push(bytes[n] >> (8 - remain));
    }
    result
}
// incs(X)=MSBlen(X)-s(X) || [int(LSBs(X))+1 mod 2^s]s
fn inc_32(bits: u128) -> u128 {
    let msb = bits >> 32;
    let mut lsb = (bits & 0xffffffff) as u32;
    lsb = lsb.wrapping_add(1);
    msb << 32 | lsb as u128
}
fn ghash(key: u128, messages: &[u128]) -> u128 {
    let mut y = 0u128;
    for i in 0..messages.len() {
        let yi = gmul_128(y ^ messages[i], key);
        y = yi;
    }
    y
}
impl GCM {
    pub fn new(key: [u8; 16]) -> Self {
        let aes = AES::new(&key);
        Self {
            aes,
        }
    }
    fn ghash_key(&mut self) -> u128 {
        u8to128(&self.aes.encrypt_block(&[0u8; 16]))
    }
    pub fn gctr(&mut self, iv: u128, message: &[u8]) -> Vec<u8> {
        // 如果X是空串, 则直接返回
        if message.len() == 0 {
            return vec![];
        }
        let mut output = vec![];
        let mut cb = iv;
        for chunk in message.chunks(16) {
            if chunk.len() < 16 {
                let msb = msb_s(chunk.len() * 8, self.aes.encrypt_block(&cb.to_be_bytes().try_into().expect("")));
                let y = u8to128(chunk) ^ u8to128(&msb);
                output.extend_from_slice(&y.to_be_bytes()[16 - chunk.len()..16])
            } else {
                let y = u8to128(chunk) ^ u8to128(&self.aes.encrypt_block(&cb.to_be_bytes().try_into().expect("")));
                output.extend_from_slice(&y.to_be_bytes());
            }
            // counter + 1
            cb = inc_32(cb);
        }
        output
    }
    pub fn ae(&mut self, iv_bytes: &[u8], adata: &[u8], message: &[u8]) -> (Vec<u8>, Vec<u8>) {
        let ghash_key = self.ghash_key();
        let mut iv_padding = vec![];
        let iv = u8to128(iv_bytes);
        let j0 = match iv_bytes.len() == 12 {
            true => {
                iv << 32 | 0x00000001
            }
            false => {
                // s = 128[len(iv) / 128] - len(iv)
                let s = 128 * (((iv_bytes.len() * 8) + 128 - 1) / 128) - (iv_bytes.len() * 8);
                iv_padding.push(iv << s);
                iv_padding.push((iv_bytes.len() * 8) as u128);
                ghash(ghash_key, &iv_padding)
            }
        };
        let message_len = message.len() * 8;
        let adata_len = adata.len() * 8;
        let u = 128 * ((message_len + 128 - 1) / 128) - message_len;
        let v = 128 * ((adata_len + 128 - 1) / 128) - adata_len;
        println!("u, v: {}, {}", u, v);
        println!("j0 = {:02x?}", j0);
        let enc = self.gctr(inc_32(j0), &message);
        let mut bit_string = Vec::<u8>::new();
        bit_string.extend_from_slice(adata);
        bit_string.extend_from_slice(&vec![0x00; v / 8]);
        // 这里认证的是密文
        bit_string.extend_from_slice(&enc);
        bit_string.extend_from_slice(&vec![0x00; u / 8]);
        bit_string.extend_from_slice(&(adata_len as u64).to_be_bytes());
        bit_string.extend_from_slice(&(message_len as u64).to_be_bytes());
        println!("len = {}, bit_string[u8] = {:02x?}", bit_string.len(), bit_string);
        let bit_string: Vec<u128> = bit_string.chunks(16).map(|it| u8to128(it)).collect();
        println!("bit_string[u128] = {:02x?}", bit_string);
        let s = ghash(ghash_key, &bit_string).to_be_bytes();
        println!("{:02x?}", s);
        let tag = self.gctr(j0, &s);
        // println!("tag = {:02x?}", tag);
        // println!("enc = {:02x?}", enc);
        (tag, enc)
    }
}
#[cfg(test)]
mod tests {
    use crate::{GCM, u8to128};
    use hex_literal::hex;
    // example from https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
    #[test]
    fn test_u8to128() {
        let bytes = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x01
        ];
        assert_eq!(u8to128(&bytes), 1);
    }
    #[test]
    fn test_gcm_ae() {
        let key: [u8; 16] = hex!("00000000000000000000000000000000");
        let p: Vec<u8> = vec![];
        let iv = hex!("000000000000000000000000");
        let mut gcm = GCM::new(key);
        let h = gcm.ghash_key();
        assert_eq!(h, u8to128(&hex!("66e94bd4ef8a2c3b884cfa59ca342b2e")));
        gcm.ae(&iv, &[], &p);
    }
    #[test]
    fn test_gcm_ae_test_case_two() {
        let key: [u8; 16] = hex!("00000000000000000000000000000000");
        let p = hex!("00000000000000000000000000000000");
        let iv = hex!("000000000000000000000000");
        let mut gcm = GCM::new(key);
        let h = gcm.ghash_key();
        assert_eq!(h, u8to128(&hex!("66e94bd4ef8a2c3b884cfa59ca342b2e")));
        let (tag, enc) = gcm.ae(&iv, &[], &p);
        println!("tag = {:02x?}", tag);
        println!("enc = {:02x?}", enc);
    }
    #[test]
    fn test_gcm_ae_test_case_three() {
        let key: [u8; 16] = hex!("feffe9928665731c6d6a8f9467308308");
        let p = hex!("d9313225f88406e5a55909c5aff5269a
                        86a7a9531534f7da2e4c303d8a318a72
                        1c3c0c95956809532fcf0e2449a6b525
                        b16aedf5aa0de657ba637b391aafd255");
        let iv = hex!("cafebabefacedbaddecaf888");
        let mut gcm = GCM::new(key);
        let (tag, enc) = gcm.ae(&iv, &[], &p);
        println!("tag = {:02x?}", tag);
        println!("enc = {:02x?}", enc);
    }
    #[test]
    fn test_gcm_ae_test_case_four() {
        let key: [u8; 16] = hex!("feffe9928665731c6d6a8f9467308308");
        let p = hex!("d9313225f88406e5a55909c5aff5269a
                        86a7a9531534f7da2e4c303d8a318a72
                        1c3c0c95956809532fcf0e2449a6b525
                        b16aedf5aa0de657ba637b39");
        let iv = hex!("cafebabefacedbaddecaf888");
        let adata = hex!("feedfacedeadbeeffeedfacedeadbeefabaddad2");
        let mut gcm = GCM::new(key);
        let (tag, enc) = gcm.ae(&iv, &adata, &p);
        println!("tag = {:02x?}", tag);
        println!("enc = {:02x?}", enc);
    }
}


相关文章
|
6月前
|
机器学习/深度学习 算法 安全
密码学系列之五:MD5、SHA1——一文搞懂哈希函数
密码学系列之五:MD5、SHA1——一文搞懂哈希函数
3487 0
|
存储 Rust 并行计算
【密码学】一文读懂XTS模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
5113 0
【密码学】一文读懂XTS模式
|
Rust 算法 安全
【密码学】一文读懂HMAC
本文将来聊一聊基于哈希函数的消息认证码,在此之前,先来科普一下什么是 「消息认证码」 (MAC), 先来看一个简单的栗子
1668 0
【密码学】一文读懂HMAC
|
5月前
|
存储 算法 安全
深入解析RSA算法原理及其安全性机制
深入解析RSA算法原理及其安全性机制
|
6月前
|
算法 安全 网络安全
一文搞懂常见的加密算法
一文搞懂常见的加密算法
812 0
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂RSA的随机数生成器
本文接着来聊一个比较轻松的内容,再来说一个随机数生成器,对于这个随机数生成器呢,这里和之前讲到过的BBS有一些类似,直接来看具体的内容蛤。
1109 1
【密码学】一文读懂RSA的随机数生成器
|
算法 安全 数据安全/隐私保护
RSA加密算法概述流程+伪代码
RSA加密算法概述流程+伪代码
374 0
|
算法 数据安全/隐私保护
【密码学】一文读懂SHA-1
SHA-1(Secure Hash Algorithm 1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦资料处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。
1209 1
【密码学】一文读懂SHA-1
|
并行计算 搜索推荐 算法
【密码学】分组密码模式
分组密码只能处理固定长度的分组,但是对于如何把每个分组组合起来,这里面也会有多种方案,一个非常简单的做法便是每个分组单独处理,然后把每个分组的结果拼接起来,这种方式其实也就是「ECB」模式,这个模式实际上是非常不安全的,各位读者在生产环境千万不要使用,本文在加下将描述几种模式。
【密码学】分组密码模式
|
算法 Serverless 数据安全/隐私保护
【密码学】一文读懂SHA-2
SHA-2安全散列算法2(Secure Hash Algorithm 2)一种密码散列函数算法标准,由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
1026 0
【密码学】一文读懂SHA-2