序列密码(Trivium)

简介: 出师一表真名世,千载谁堪伯仲间!-- 陆游

序列密码(Trivium)


出师一表真名世,千载谁堪伯仲间!-- 陆游

QS_{D}QCZ6HFL1Y[{(B7AZG.jpg

Trivium

Trivium密码是一种对称密钥同步序列密码算法。它的设计目的是在计算能力有限的硬件上高效实现安全加密,同时兼顾软件实现效率。

image.gifTrivium加密过程(此图参考自深入浅出密码学一书)

1W`8%DEJLXOAD4[L2`Q%DX0.png


加密过程

  1. 初始化
  • 将80位的密钥key放到寄存器A前面80位
  • 将80位的初始化向量iv放到寄存器B前面80位
  • 除了放置密钥的位和寄存器C最后3位,所有寄存器其余位置置0
  1. 热身阶段

在这一个阶段,循环 4 * 288 = 1152 次密钥生成算法(注:这一阶段不进行任何输出), 其目的是为了充分随机化密码,也确保密钥序列同时取决于kIV

  1. 加密阶段

后续就可以连续调用密钥生成算法,也就是说从1153位开始生成的stream,即可作为密钥序列。

密钥流生成算法

for i = 1 to N do
    t1 ← s66 + s93
    t2 ← s162 + s177
    t3 ← s243 + s288
    zi ← t1 + t2 + t3
    t1 ← t1 + s91 · s92 + s171
    t2 ← t2 + s175 · s176 + s264
    t3 ← t3 + s286 · s287 + s69
    (s1, s2, . . . , s93) ← (t3, s1, . . . , s92)
    (s94, s95, . . . , s177) ← (t1, s94, . . . , s176)
    (s178, s279, . . . , s288) ← (t2, s178, . . . , s287)
end for

需要注意,这里的运算都是基于GF(2)上的运算。


算法实现

依然采用Rust实现

use std::iter::repeat;
pub struct Trivium {
    lfsr_a: [u32; 3],
    lfsr_b: [u32; 3],
    lfsr_c: [u32; 4],
}
fn u8to32_be(p: &[u8; 4]) -> u32 {
    return ((p[0] as u32) << 24 | ((p[1] as u32) << 16) | ((p[2] as u32) << 8) | p[3] as u32) as u32;
}
fn revert_u32(p: u32) -> u32 {
    return p << 24 | ((p << 8) & 0x00ff0000) | ((p >> 8) & 0x0000ff00) | p >> 24;
}
fn s64(p: &[u32], b: u32) -> u32 {
    return p[1] << (b - 64) | p[2] >> (96 - b);
}
fn s96(p: &[u32], b: u32) -> u32 {
    return p[2] << (b - 96) | p[3] >> (128 - b);
}
fn rotate_lfsr_3(p: &mut [u32; 3], t: u32) {
    p[2] = p[1];
    p[1] = p[0];
    p[0] = t;
}
fn rotate_lfsr_4(p: &mut [u32; 4], t: u32) {
    p[3] = p[2];
    p[2] = p[1];
    p[1] = p[0];
    p[0] = t;
}
impl Trivium {
    pub fn init(key: [u8; 10], iv: [u8; 10]) -> Trivium {
        let mut trivium = Trivium {
            lfsr_a: [0; 3],
            lfsr_b: [0; 3],
            lfsr_c: [0; 4],
        };
        trivium.lfsr_a[0] = u8to32_be(&[key[0], key[1], key[2], key[3]]);
        trivium.lfsr_a[1] = u8to32_be(&[key[4], key[5], key[6], key[7]]);
        trivium.lfsr_a[2] = u8to32_be(&[key[8], key[9], 0, 0]);
        trivium.lfsr_b[0] = u8to32_be(&[iv[0], iv[1], iv[2], iv[3]]);
        trivium.lfsr_b[1] = u8to32_be(&[iv[4], iv[5], iv[6], iv[7]]);
        trivium.lfsr_b[2] = u8to32_be(&[iv[8], iv[9], 0, 0]);
        trivium.lfsr_c[0] = 0;
        trivium.lfsr_c[1] = 0;
        trivium.lfsr_c[2] = 0;
        trivium.lfsr_c[3] = 0x000e0000;
        // Initialization: 1142
        for _ in 0..36 {
            let mut t1 = s64(&trivium.lfsr_a, 66) ^ s64(&trivium.lfsr_a, 93);
            let mut t2 = s64(&trivium.lfsr_b, 69) ^ s64(&trivium.lfsr_b, 84);
            let mut t3 = s64(&trivium.lfsr_c, 66) ^ s96(&trivium.lfsr_c, 111);
            t1 ^= (s64(&trivium.lfsr_a, 91) & s64(&trivium.lfsr_a, 92)) ^ s64(&trivium.lfsr_b, 78);
            t2 ^= (s64(&trivium.lfsr_b, 82) & s64(&trivium.lfsr_b, 83)) ^ s64(&trivium.lfsr_c, 87);
            t3 ^= (s96(&trivium.lfsr_c, 109) & s96(&trivium.lfsr_c, 110)) ^ s64(&trivium.lfsr_a, 69);
            // Rotate LFSRs
            rotate_lfsr_3(&mut trivium.lfsr_a, t3);
            rotate_lfsr_3(&mut trivium.lfsr_b, t1);
            rotate_lfsr_4(&mut trivium.lfsr_c, t2);
        }
        return trivium;
    }
    fn generate_key_stream(&mut self, output: &mut [u32]) {
        for i in output.iter_mut() {
            let mut t1 = s64(&self.lfsr_a, 66) ^ s64(&self.lfsr_a, 93);
            let mut t2 = s64(&self.lfsr_b, 69) ^ s64(&self.lfsr_b, 84);
            let mut t3 = s64(&self.lfsr_c, 66) ^ s96(&self.lfsr_c, 111);
            *i = revert_u32(t1 ^ t2 ^ t3);
            t1 ^= (s64(&self.lfsr_a, 91) & s64(&self.lfsr_a, 92)) ^ s64(&self.lfsr_b, 78);
            t2 ^= (s64(&self.lfsr_b, 82) & s64(&self.lfsr_b, 83)) ^ s64(&self.lfsr_c, 87);
            t3 ^= (s96(&self.lfsr_c, 109) & s96(&self.lfsr_c, 110)) ^ s64(&self.lfsr_a, 69);
            // Rotate LFSRs
            rotate_lfsr_3(&mut self.lfsr_a, t3);
            rotate_lfsr_3(&mut self.lfsr_b, t1);
            rotate_lfsr_4(&mut self.lfsr_c, t2);
        }
    }
    fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
        let mut key_stream: Vec<u32> = repeat(0).take(output.len() / 4 + 1).collect();
        self.generate_key_stream(&mut key_stream);
        for (i, (j, k)) in input.iter().zip(output.iter_mut()).enumerate() {
            *k = *j ^ (match i % 4 {
                0 => key_stream[i / 4] >> 24,
                1 => key_stream[i / 4] >> 16,
                2 => key_stream[i / 4] >> 8,
                3 => key_stream[i / 4],
                _ => panic!() // 正常应该不会匹配到这里,但是吧这个不写编译不过,也许会有更优雅的方案。
            }) as u8;
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::trivium::{Trivium};
    use std::iter::repeat;
    #[test]
    fn test() {
        let mut trivium = Trivium::init([0; 10], [255; 10]);
        let plaintext = "plaintext";
        let mut result: Vec<u8> = repeat(0).take(plaintext.len()).collect();
        trivium.crypt(plaintext.as_bytes(), &mut result);
        println!("{:?}", result);
        let mut trivium = Trivium::init([0; 10], [255; 10]);
        let mut output: Vec<u8> = repeat(0).take(plaintext.len()).collect();
        trivium.crypt(&result, &mut output);
        println!("{:?}", output);
    }
}

代码仅供参考学习使用(简单的实现),未经过严格测试,请不要直接用于生产用途,避免造成损失


算法应用

Trivium算法在设计之初就是完全出于硬件效率考虑。由于采用了线性反馈移位寄存器和简单的AND函数,其硬件实现效率很高。下面来具体应用一下这个算法,看一下编译之后这个算法有什么特征,下面写一个简单的例子,这里采取ndk的实现,所以先来用c++实现一下Trivium算法吧,代码写的比较low, 请大佬们海涵。

#define HIGH_MASK 0xFFFF0000
uint32_t u8to32(const uint8_t *p) {
    return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
uint32_t revert_u32(uint32_t p) {
    return p << 24 | ((p << 8) & 0x00ff0000) | ((p >> 8) & 0x0000ff00) | p >> 24;
}
uint32_t s64(uint32_t *p, int b) {
    return p[1] << (b - 64) | p[2] >> (96 - b);
}
uint32_t s96(uint32_t *p, int b) {
    return p[2] << (b - 96) | p[3] >> (128 - b);
}
void rotate_lfsr_3(uint32_t *p, int t) {
    p[2] = p[1];
    p[1] = p[0];
    p[0] = t;
}
void rotate_lfsr_4(uint32_t *p, int t) {
    p[3] = p[2];
    p[2] = p[1];
    p[1] = p[0];
    p[0] = t;
}
Trivium::Trivium(uint8_t *key, uint8_t *iv) {
    this->lfsr_a[0] = u8to32(key);
    this->lfsr_a[1] = u8to32(key + 4);
    this->lfsr_a[2] = u8to32(key + 8) & HIGH_MASK;
    this->lfsr_b[0] = u8to32(iv);
    this->lfsr_b[1] = u8to32(iv + 4);
    this->lfsr_b[2] = u8to32(iv + 8) & HIGH_MASK;
    this->lfsr_c[0] = 0;
    this->lfsr_c[1] = 0;
    this->lfsr_c[2] = 0;
    this->lfsr_c[3] = 0x000E0000;
    // Initialization: 1142
    for (int i = 0; i < 36; ++i) {
        this->generateKeyStream();
    }
}
uint32_t Trivium::generateKeyStream() {
    uint32_t t1, t2, t3, output;
    t1 = s64(this->lfsr_a, 66) ^ s64(this->lfsr_a, 93);
    t2 = s64(this->lfsr_b, 69) ^ s64(this->lfsr_b, 84);
    t3 = s64(this->lfsr_c, 66) ^ s96(this->lfsr_c, 111);
    output = revert_u32(t1 ^ t2 ^ t3);
    t1 ^= (s64(this->lfsr_a, 91) & s64(this->lfsr_a, 92)) ^ s64(this->lfsr_b, 78);
    t2 ^= (s64(this->lfsr_b, 82) & s64(this->lfsr_b, 83)) ^ s64(this->lfsr_c, 87);
    t3 ^= (s96(this->lfsr_c, 109) & s96(this->lfsr_c, 110)) ^ s64(this->lfsr_a, 69);
    rotate_lfsr_3(this->lfsr_a, t3);
    rotate_lfsr_3(this->lfsr_b, t1);
    rotate_lfsr_4(this->lfsr_c, t2);
    return output;
}
void Trivium::encrypt(char *input, char output[]) {
    size_t count = ceil(strlen(input) / 4.0);
    int ptr = 0;
    for (int i = 0; i < count; ++i) {
        uint32_t stream = this->generateKeyStream();
        output[ptr] = input[ptr] ^ (stream >> 24);
        output[ptr + 1] = input[ptr + 1] ^ (stream >> 16);
        output[ptr + 2] = input[ptr + 2] ^ (stream >> 8);
        output[ptr + 3] = input[ptr + 3] ^ stream;
        ptr += 4;
    }
}

然后简单实现一个jni的方法,这里我偷懒了,没有对密钥长度和初始化向量的长度进行一个校验,这里要求是80bit。

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_littleq_cryptography_MainActivity_trivium(
        JNIEnv *env,
        jobject thiz,
        jbyteArray key,
        jbyteArray iv,
        jbyteArray content) {
    size_t key_size = env->GetArrayLength(key);
    size_t iv_size = env->GetArrayLength(iv);
    size_t content_size = env->GetArrayLength(content);
    auto *key_dst = (jbyte *) malloc(key_size * sizeof(jbyte));
    auto *iv_dst = (jbyte *) malloc(iv_size * sizeof(jbyte));
    auto *content_dst = (jbyte *) malloc(content_size * sizeof(jbyte));
    env->GetByteArrayRegion(key, 0, key_size, key_dst);
    env->GetByteArrayRegion(iv, 0, iv_size, iv_dst);
    env->GetByteArrayRegion(content, 0, content_size, content_dst);
    uint8_t _key[10] = {0};
    uint8_t _iv[10] = {0};
    for (int i = 0; i < key_size; ++i) {
        _key[i] = key_dst[i];
        _iv[i] = iv_dst[i];
    }
    auto *trivium = new Trivium(_key, _iv);
    char output[9] = {0};
    trivium->encrypt(reinterpret_cast<char *>(content_dst), output);
    jbyteArray byteArray = env->NewByteArray(content_size);
    env->SetByteArrayRegion(byteArray, 0, content_size, (jbyte *) output);
    return byteArray;
}

Java 层代码如下, 这里仅仅贴出核心的代码:

public class MainActivity extends AppCompatActivity {
    // ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        byte[] key = new byte[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
        byte[] iv = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
        byte[] output = this.trivium(key, iv, "plaintext".getBytes());
        for (byte b : output) {
            Log.i("LQ", String.valueOf(b & 0xFF)); // 输出一下结果
        }
    }
    public native byte[] trivium(byte[] key, byte[] iv, byte[] content);
}


然后直接编译出apk, 解压拿到so, 扔进ida先看看(虽然这里代码是我自己写的,就当我先不知道这块算法的逻辑蛤)。

IFJ2]%K(51)TNX5[@[_[QVA.png

简单做一下重命名,从这里来看整个核心逻辑还是比较清晰的,这里class的名称没有隐藏,先忽略一下哈,先看一下构造函数。

image.gif9{LZP%2D[1((Z@}FRO3_[`F.png

这里可以发现一个特征,就是Trivium需要进行一个1152轮的迭代运算,这里因为是存储按照uint_32来的,因此是迭代了36轮,但是如果使用这个算法,默认丢掉1153之后的个值的话,这就不一定靠谱了。

接下来看一下密钥生成算法,也就是加密之后的每一个bit是怎么来的,这里其实都是基于uint_32来做的运算。

@59LC1TEUA)F%%%ONU{A3QW.png

这里编译器做了优化,直接把我写的函数给展开了,从这里可以发现这个算法流程还是比较清晰的,直接生成一个32bit的密钥流。

L0$YL9O[{`54@TVO[(9A%$4.jpg

最后看一下实际的加密函数,这里我修改了一下入参的名称,从这里来看,可以看到是一个序列密码的特征,因为序列密码是对于每个位进行加密,然后去看密钥流生成算法就可以大概判定是用的那种加密方式了。

相关文章
|
Web App开发 Rust 算法
【密码学】一文读懂ChaCha20
好久没写新的加密算法的原理了, 这次所选取的加密算法结构比较简单, 一起来看一下吧。
5773 0
【密码学】一文读懂ChaCha20
|
存储 Rust 并行计算
【密码学】一文读懂XTS模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
3603 0
【密码学】一文读懂XTS模式
|
7月前
|
算法 数据安全/隐私保护 C语言
XXTEA加密算法
XXTEA加密算法
194 0
|
算法 网络安全 数据安全/隐私保护
【密码学】手摸手带你手算AES
本文带着大家手动计算了一下完整的简化版AES的整个流程,其实主要都参考了密码学与网络安全这本书,大部分的公式都是从这本书上来的,我是真的喜欢这个例子,麻雀虽小,五脏俱全,用来学习AES的入门感觉非常的合适,如果能够完整的自己手算下来这个例子,然后再去看完整版的AES算法,会简单非常多,主要就是规模扩大了一下,核心的流程还是上面这一套。最后,感谢能看到这里的读者,如果本文对大佬们理解AES有一点点的帮助,也不枉我手动敲了这么多的公式和矩阵了。
【密码学】手摸手带你手算AES
|
Rust 算法 Go
【密码学】一文读懂MurMurHash3
本文应该是MurMurHash算法介绍的最后一篇,来一起看一下最新的MurMurHash算法的具体过程,对于最新的算法来说,整个流程和之前的其实也比较相似,这里从维基百科当中找到了伪代码,也就不贴出来Google官方给出的推荐代码了,先来看一下维基百科给出的伪代码,这里只有32位的伪代码。
1274 0
【密码学】一文读懂MurMurHash3
|
Rust 算法 安全
【密码学】一文读懂MurMurHash2
上次我们聊过了一代的MurMurHash算法,是的,我又来水文章了,今天呢,接着来聊一下二代的MurMurHash算法,二代算法的整体结构实际上和一代算法差不太多,只是对于每一轮数据的处理过程当中的运算有一些差异,算法的来源依然是来自于Google官网给提供的源码,对着源码看的结构,对于这个算法呢,有两个版本,一个是32位的,一个是64位的,对于32位的算法和64位的算法,区别在于两个初始的魔数不同,整体运算过程还是十分相似的。
1722 0
【密码学】一文读懂MurMurHash2
|
Rust 算法 网络安全
【密码学】一文读懂CMAC
介于上一篇文章比较水,然后这个和上一篇也比较相似,CMAC是为了解决DAA当中安全性不足的问题而出现的,这个算法一共有三个密钥,K, K1, K2, 其中K1和K2可以由K导出,接下来就来一起看一下CMAC的具体过程吧,这一篇文章其实也不长。
2863 0
【密码学】一文读懂CMAC
|
Rust 算法 安全
【密码学】一文读懂HMAC
本文将来聊一聊基于哈希函数的消息认证码,在此之前,先来科普一下什么是 「消息认证码」 (MAC), 先来看一个简单的栗子
1034 0
【密码学】一文读懂HMAC
|
安全 算法 Unix
使用CPU硬件指令对AES加解密进行加速
###概述 AES是世界上最安全、使用广泛的加密算法,很多安全合规要求里面都明确要求使用AES算法,只是相对于3des、rc4等加密算法,速度慢了很多,幸好有了AES-NI,这是针对AES加密算法的硬件加解密CPU指令集。 AES-NI的全称是:Advanced Encryption Standard New Instructions。[指令集说明](https://en.wikipedi
23644 0
|
算法 安全 Go
【密码学】一文读懂HKDF
我这又来水一篇文章,来聊一下HKDF(基于HMAC的密钥导出函数)。密钥派生函数是密钥管理的组成部分,他的目标是通过一些初始的数据派生出来密码学安全的随机密钥。
2785 1
【密码学】一文读懂HKDF