16bitのRSAを実装してみる (C)
- 昔実装した16bitのRSAが見事に間違っていたことに気付き、狼狽した勢いで書き直しました。
- 実用性はありませんが、仕組みを理解するために作成
- 仕様
- 平文 ... 16ビットの符号なし整数 (或いは2バイトのデータ) とする。
- 平文の最上位ビットを1にするなどして値の大きさを確保するらしいので、実質的には16ビット未満?
- 平文 ... 16ビットの符号なし整数 (或いは2バイトのデータ) とする。
ソースコード
rsa.c
#include <stdio.h> typedef unsigned __int8 uchar; typedef unsigned __int32 uint; typedef unsigned __int64 uint64; #define SWAP(a, b) (a ^= b, b ^= a, a ^= b) // VC ----> #include <windows.h> #include <wincrypt.h> #pragma comment(lib, "ADVAPI32") /* CSPRNG 0 以上 2^16 未満の乱数を返す。 */ static uint CRandom16(void) { uchar v[2]; { HCRYPTPROV hp; if(!CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) exit(1); // fatal if(!CryptGenRandom(hp, 2, v)) exit(1); // fatal CryptReleaseContext(hp, 0); } return (uint)v[0] << 8 | v[1]; } // <---- VC /* ユークリッド互除法 (x, y) の最大公約数を返す。 */ static uint GCD(uint x, uint y) { while(y) { x %= y; SWAP(x, y); } return x; } /* 拡張ユークリッド互除法 (x, y) の最大公約数を返す。 (*a) * x + (*b) * y === GCD(x, y) (mod m) となるような (*a), (*b) をセットする。 */ static uint ExtGCD(uint x, uint y, uint *a, uint *b, uint m) { uint ret; if(y) { ret = ExtGCD(y, x % y, b, a, m); *b += m - ((uint64)(x / y) * (*a)) % m; *b %= m; } else { ret = x; *a = 1; *b = 0; } return ret; } /* (v ^ e) % m を返す。 */ static uint ModPow(uint v, uint e, uint m) { uint64 r; if(!e) return 1; r = ModPow(v, e / 2, m); r *= r; r %= m; if(e & 1) { r *= v; r %= m; } return r; } /* 1 以上 2^16 未満の奇数について、素数でなければ真を返す。 */ static int IsComposite(uint value) { static uchar ops[0x1000]; #define op(p) (ops[(p) / 16] & 1 << (p) / 2 % 8) if(!ops[0]) { uint c; uint d; ops[0] = 1; for(c = 3; c < 0x100; c += 2) if(!op(c)) for(d = c * c / 2; d < 0x10000 / 2; d += c) ops[d / 8] |= 1 << d % 8; } return op(value); #undef op } // // ====================== // ==== ここから RSA ==== // ====================== // /* 鍵生成 (*e) <--- 暗号鍵のext (*d) <--- 復号鍵のext (*m) <--- mod をそれぞれセットする。 ( (*e), (*m) ) を暗号鍵として使用する。 ( (*d), (*m) ) を復号鍵として使用する。 */ static void GenKey(uint *e, uint *d, uint *m) { uint p; uint q; uint tmp; do { while(p = CRandom16() | 1, IsComposite(p)); while(q = CRandom16() | 1, p == q || IsComposite(q)); } while(p * q < 0x10000); *m = p * q; p--; q--; p *= q / GCD(p, q); while(*e = CRandom16() | 1, IsComposite(*e) || ExtGCD(*e, p, d, &tmp, p) != 1); } /* 暗号化 p を平文 (e, m) を暗号鍵とする。 */ static uint Enc(uint p, uint e, uint m) { return ModPow(p, e, m); } /* 復号 c を暗号文 (d, m) を復号鍵とする。 */ static uint Dec(uint c, uint d, uint m) { return ModPow(c, d, m); } // // ====================== // ==== ここまで RSA ==== // ====================== // /* 簡単なテスト */ static void DoTest(void) { uint e; uint d; uint m; uint p; uint c; uint q; GenKey(&e, &d, &m); printf("暗号鍵 = %u (%u)\n", e, m); printf("復号鍵 = %u (%u)\n", d, m); p = CRandom16(); printf("平文 = %u\n", p); c = Enc(p, e, m); printf("暗号文 = %u\n", c); q = Dec(c, d, m); printf("復号した平文 = %u\n", q); if(p == q) printf("■成功!\n"); else printf("□失敗!\n"); printf("\n"); } int main() { uint c; for(c = 0; c < 10; c++) { DoTest(); } }
実行結果 (出力例)
暗号鍵 = 40519 (850836101) 復号鍵 = 89955727 (850836101) 平文 = 31043 暗号文 = 175750552 復号した平文 = 31043 ■成功! 暗号鍵 = 22447 (34618007) 復号鍵 = 9042127 (34618007) 平文 = 18870 暗号文 = 32960880 復号した平文 = 18870 ■成功! 暗号鍵 = 20231 (861475339) 復号鍵 = 125032867 (861475339) 平文 = 1253 暗号文 = 221583236 復号した平文 = 1253 ■成功! 暗号鍵 = 51713 (530318947) 復号鍵 = 124438833 (530318947) 平文 = 30961 暗号文 = 265166145 復号した平文 = 30961 ■成功! 暗号鍵 = 15817 (1299805879) 復号鍵 = 394184833 (1299805879) 平文 = 25409 暗号文 = 254132619 復号した平文 = 25409 ■成功! 暗号鍵 = 7933 (1367940043) 復号鍵 = 242017 (1367940043) 平文 = 38417 暗号文 = 226452582 復号した平文 = 38417 ■成功! 暗号鍵 = 41911 (427479329) 復号鍵 = 207319555 (427479329) 平文 = 28550 暗号文 = 74597128 復号した平文 = 28550 ■成功! 暗号鍵 = 5431 (847469081) 復号鍵 = 65065219 (847469081) 平文 = 52017 暗号文 = 774888864 復号した平文 = 52017 ■成功! 暗号鍵 = 7309 (1558907309) 復号鍵 = 722680363 (1558907309) 平文 = 680 暗号文 = 1035114472 復号した平文 = 680 ■成功! 暗号鍵 = 9221 (3715798837) 復号鍵 = 4029581 (3715798837) 平文 = 7644 暗号文 = 3582662490 復号した平文 = 7644 ■成功!
平文と復号した平文が一致することしか見てないけど、ちゃんと実装できている。多分。...と思う。...と信じたい。
アルゴリズム (ほとんど自分用メモ)
鍵生成 (GenKey関数)
do { while(p = CRandom16() | 1, IsComposite(p)); // p が奇素数になるまで繰り返す。 while(q = CRandom16() | 1, p == q || IsComposite(q)); // q が p 以外の奇素数になるまで繰り返す。 } while(p * q < 0x10000); // p * q (法n)が 16 bit に満たない場合はやり直す。
- 異なる16ビットの奇素数を p, q とする。
*m = p * q;
- m(法n)
p--;
q--;
p *= q / GCD(p, q);
- (p - 1) * (q - 1) / GCD(p - 1, q - 1) を求め p にセットする。
- 0 以上 m 未満の整数 x について x^p % m == 1 が成り立つ。⇒ 何でだっけ... ⇒ フェルマーの小定理?
- なので (n ^ (p + 1)) % m == n も成り立つ。
- なので e * d == (p の倍数 + 1) となるような e, d を求めれば (((x ^ e) % m) ^ d) % m == x という風に使える。
- x が平文
- (x ^ e) % m が暗号文
while(*e = CRandom16() | 1, IsComposite(*e) || ExtGCD(*e, p, d, &tmp, p) != 1);
- p と互いに素な e を求める。
- GCD(e, p) == 1 なので、ExtGCD の第3引数 a の指す先には e * d == (p の倍数 + 1) となるような d がセットされる。
- (*a) * x + (*b) * y == 1 (mod m) ということは ((*a) * x) % y == 1 なので
- 本実装における ExtGCD は (*a), (*b) に m を法とする整数をセットしてくれるので、(*a) をそのまま d として使える。
暗号化 (Enc関数)
- 暗号文 = 平文 ^ e mod m
- 平文は 16 ビットの符号無し整数
復号 (Dec関数)
- 平文 = 暗号文 ^ d mod m