安全でないストリーム暗号ARCFOUR

はじめに

Wikipediaを見てみたらARCFOURのアルゴリズムが単純だったので実装してみた. 一応言っておくが, 暗号アルゴリズムの実装は素人がやるものではない.

参考記事: https://en.wikipedia.org/wiki/RC4

記事に書いてある通りに実装した.

ソースコード

/* This program is in the public domain */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>

#define MAX_KEY_LEN 256
#define S_LEN 256

uint8_t key[MAX_KEY_LEN + 1];
uint8_t S[S_LEN];

void swap(uint8_t *a, uint8_t *b)
{
    uint8_t t = *a;
    *a = *b;
    *b = t;
}

char *readline(char *s, size_t size)
{
    if (fgets(s, size, stdin) == NULL)
        return NULL;
    if (strchr(s, '\n') != NULL)
        s[strlen(s) - 1] = '\0';
    else
        while (getchar() != '\n')
            ;
    return s;
}

/* key-scheduling algorithm */
void KSA(size_t key_len)
{
    size_t i, j;
    for (i = 0; i < S_LEN; i++)
        S[i] = i;
    j = 0;
    for (i = 0; i < S_LEN; i++) {
        j = (j + S[i] + key[i % key_len]) % S_LEN;
        swap(&S[i], &S[j]);
    }
}

/* pseudo-random generation algorithm (PRGA) */
uint8_t PRGA(void)
{
    /* static variables! */
    static size_t i, j;
    i = (i + 1) % S_LEN;
    j = (j + S[i]) % S_LEN;
    swap(&S[i], &S[j]);
    return S[(S[i] + S[j]) % S_LEN];
}

int main(int argc, char **argv)
{
    FILE *fr, *fw;
    uint8_t buf;
    size_t key_len;

    if (argc != 3) {
        fprintf(stderr, "specify files\n");
        return 1;
    }
    if (!readline((char *)key, MAX_KEY_LEN + 1)) {
        fprintf(stderr, "failed to read key");
        return 1;
    }
    key_len = strlen((char *)key);
    KSA(key_len);
    if ((fr = fopen(argv[1], "rb")) == NULL) {
        fprintf(stderr, "failed to open file");
        return 1;
    }
    if ((fw = fopen(argv[2], "wb")) == NULL) {
        fprintf(stderr, "failed to open file");
        fclose(fr);
        return 1;
    }
    for (;;) {
        fread(&buf, sizeof(buf), 1, fr);
        if (feof(fr))
            break;
        if (ferror(fr)) {
            fprintf(stderr, "failed to read\n");
            break;
        }
        buf ^= PRGA();
        if (fwrite(&buf, sizeof(buf), 1, fw) < 1) {
            fprintf(stderr, "failed to write\n");
            break;
        }
    }
    fclose(fr);
    fclose(fw);
    return 0;
}

動作テスト

テキストファイルtest.txtを暗号化する. 暗号化すると文字化けするので16進数で表示する.

$ hexdump test.txt                                                                
0000000 83e3 e390 bc83 83e3 e381 a383 83e3 e3ab                                                         
0000010 a983 82e3 e3a4 9083 83e3 e3bc ae81 9ce6                                                         
0000020 e388 8e83 bee7 e58e 8e85 81e3 e3a7 9981                                                         
0000030 80e3 0a82 81e3 e3bf a881 83e3 e3a9 b882                                                         
0000040 81e3 e8ae b3a9 b4e7 e3b0 af81 81e3 e393                                                         
0000050 a181 82e3 ef89 81bc 86e2 6892 7474 3a70                                                         
0000060 2f2f 7774 6670 6a2e 2f70 694d 6f74 7354                                                         
0000070 6b75 6e69 0a6f 4d44 81e3 e5af 8b80 88e5                                                         
0000080 e3a5 a781 81e3 e3af aa81 81e3 408f 696e                                                         
0000090 696a 6173 6a6e 5f69 7061 e370 be81 81e3                                                         
00000a0 e3a7 8a81 a1e9 e398 8481 87e8 e3b4 9781                                                         
00000b0 81e3 e3be 9981 80e3 0a82 83e3 e395 a182                                                         
00000c0 83e3 e3b3 a282 83e3 e3bc 8883 81e3 20af                                                         
00000d0 e323 bf81 81e3 e3a8 8281 83e3 e3bc a881                                                         
00000e0 e320 a781 99e7 e8ba 8ba6 81e3 e397 be81                                                         
00000f0 81e3 ef99 81bc 000a
00000f7

これをキーVirtual YouTuberで暗号化してtest.txt.encに保存.

$ ./arc4 test.txt test.txt.enc
Virtual YouTuber
$ hexdump test.txt.enc
0000000 ee83 d240 22d7 820f afa8 e854 2757 303f
0000010 bfed 3ebb f9a3 b055 29da b054 e975 17c2
0000020 1211 d87e 97b4 bae8 a525 3d65 7256 05e6
0000030 138a 697d c071 a7a5 e715 75be 49de 8e4c
0000040 bf07 bfff b0a4 cc27 8498 0dbd 77ea 67b1
0000050 670a 2daa a470 ab84 251f 8129 9885 41c2
0000060 afd8 57e4 2552 39ff 18db 327e 0745 399f
0000070 5c34 b6e2 c6d2 93ef c14b d111 da67 b225
0000080 fb26 fc24 d760 15fa 71e7 dce4 b55f 6d71
0000090 3355 874c e3ef 105b 423d ca88 4d9b 58ec
00000a0 f162 ef70 510a 8faa 6ef5 d474 0a03 1598
00000b0 4a35 7574 494e 2117 b50c 9898 7e58 1a4a
00000c0 93b2 4c73 4116 2b17 b2ab 4290 9d2b 107f
00000d0 1f7e d8be e6ef 0bfe 2012 f740 4595 8a8d
00000e0 ff29 84a3 a444 7c0e fbae 2515 f988 2ce9
00000f0 dcd7 7249 e9ec 002a
00000f7

test.txt.encを復号してtest.txt.enc.decに保存.

$ ./arc4 test.txt.enc test.txt.enc.dec                                            
Virtual YouTuber
$ hexdump test.txt.enc.dec                                                       
0000000 83e3 e390 bc83 83e3 e381 a383 83e3 e3ab
0000010 a983 82e3 e3a4 9083 83e3 e3bc ae81 9ce6
0000020 e388 8e83 bee7 e58e 8e85 81e3 e3a7 9981
0000030 80e3 0a82 81e3 e3bf a881 83e3 e3a9 b882
0000040 81e3 e8ae b3a9 b4e7 e3b0 af81 81e3 e393
0000050 a181 82e3 ef89 81bc 86e2 6892 7474 3a70
0000060 2f2f 7774 6670 6a2e 2f70 694d 6f74 7354
0000070 6b75 6e69 0a6f 4d44 81e3 e5af 8b80 88e5
0000080 e3a5 a781 81e3 e3af aa81 81e3 408f 696e
0000090 696a 6173 6a6e 5f69 7061 e370 be81 81e3
00000a0 e3a7 8a81 a1e9 e398 8481 87e8 e3b4 9781
00000b0 81e3 e3be 9981 80e3 0a82 83e3 e395 a182
00000c0 83e3 e3b3 a282 83e3 e3bc 8883 81e3 20af
00000d0 e323 bf81 81e3 e3a8 8281 83e3 e3bc a881
00000e0 e320 a781 99e7 e8ba 8ba6 81e3 e397 be81
00000f0 81e3 ef99 81bc 000a
00000f7

もとに戻った.

$ sha256sum test.txt test.txt.enc.dec
0e288309792d64d4b14af3db9e847de9e0c32526f2f42b9254580f86648fc3e6  test.txt
0e288309792d64d4b14af3db9e847de9e0c32526f2f42b9254580f86648fc3e6  test.txt.enc.dec

おまけ

Rustの練習で書いてみた. まったく感覚がわからない.

use std::env;
use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::process;

const MAX_KEY_LEN: usize = 256;
const S_LEN: usize = 256;

struct Arc4 {
    s: [u8; S_LEN],
    i: usize,
    j: usize,
}

// key-scheduling algorithm
fn ksa(key: &[u8]) -> Arc4 {
    let mut a = Arc4 {
        s: [0; S_LEN],
        i: 0,
        j: 0,
    };
    for i in 0..S_LEN {
        a.s[i] = i as u8;
    }
    let mut j: usize = 0;
    let key_len = key.len();
    for i in 0..S_LEN {
        j = (j + a.s[i] as usize + key[i % key_len] as usize) % S_LEN;
        a.s.swap(i, j);
    }
    a
}

// pseudo-random generation algorithm (PRGA)
fn prga(a: &mut Arc4) -> u8 {
    a.i = (a.i + 1) % S_LEN;
    a.j = (a.j + a.s[a.i] as usize) % S_LEN;
    a.s.swap(a.i, a.j);
    a.s[(a.s[a.i] as usize + a.s[a.j] as usize) % S_LEN]
}

fn main() {
    if env::args().len() != 3 {
        println!("specify files");
        process::exit(1);
    }
    let mut key_input = String::new();
    match io::stdin().read_line(&mut key_input) {
        Err(err) => println!("error: {}", err),
        _ => {},
    }
    let key_input_trimmed = key_input.trim();
    match key_input_trimmed.len() {
        1...MAX_KEY_LEN => {},
        _ => {
            println!("the key length must be 1-{} bytes (8 - {} bits)", MAX_KEY_LEN, MAX_KEY_LEN * 8);
            process::exit(1);
        }
    }
    let mut a = ksa(key_input_trimmed.as_bytes());
    let args: Vec<String> = env::args().skip(1).collect();
    let fr = File::open(&args[0]).expect("failed to open a file");
    let mut reader = BufReader::new(fr);
    let fw = File::create(&args[1]).expect("failed to create a file");
    let mut writer = BufWriter::new(fw);
    let mut buf = vec![];
    reader.read_to_end(&mut buf).expect("failed to read a file");
    for b in buf.iter_mut() {
        *b ^= prga(&mut a);
    }
    writer.write_all(&buf).expect("failed to write to a file");
}