Node.jsで公開鍵暗号方式を使う

自分用のメモです。

# 要件定義
- `~/.ssh/id_rsa` と `~/.ssh/id_rsa.pub`を使って暗号化したい
- `~/.ssh/id_rsa`は**パスワードあり**
- streamで対応したい

上記3つに対応した方法が見つからなかったので、やってみました。

# 公開鍵を使って暗号化 -> 秘密鍵を使って復号化

[ursa](https://github.com/quartzjer/ursa)モジュールが良さそうでしたので、これを利用。

```js
var ursa = require('ursa');
var fs = require('fs');
var userHome = process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
var keyPub = fs.readFileSync(userHome + '/.ssh/id_rsa.pub', 'utf8');
var pubKey = ursa.openSshPublicKey(keyPub);

// 公開鍵で暗号化
var encryptedMsg = pubKey.encrypt("Everything is going to be 200 OK", 'utf8', 'base64');
console.log(encryptedMsg); //Pk+wRhxOxjZFQ5cPlssNxqL...jt1Y4xdUQ== となる

// 秘密鍵を使って復号化
var keyPrivate = fs.readFileSync(userHome + '/.ssh/id_rsa', 'utf8');
var privateKey = ursa.createPrivateKey(keyPrivate, '<パスワード>');
var msg = privateKey.decrypt(encryptedMsg, 'base64', 'utf8');
console.log(msg);  // Everything is going to be 200 OK となる
```

# 秘密鍵を使って暗号化 -> 公開鍵を使って復号化

```js

// 秘密鍵を使って暗号化
var keyPrivate = fs.readFileSync(userHome + '/.ssh/id_rsa', 'utf8');
var privateKey = ursa.createPrivateKey(keyPrivate, '<パスワード>');
var encryptedMsg = privateKey.privateEncrypt("Everything is going to be 200 OK", 'utf8', 'base64');


// 公開鍵を使って復号化
var keyPub = fs.readFileSync(userHome + '/.ssh/id_rsa.pub', 'utf8');
var pubKey = ursa.openSshPublicKey(keyPub);
var msg = pubKey.publicDecrypt(encryptedMsg, 'base64', 'utf8');
console.log(msg);  // Everything is going to be 200 OK になる

```

# Stream APIで対応したい

[stream-buffers](https://github.com/samcday/node-stream-buffer)モジュールで200バイトずつ取得して、それを[through2](https://github.com/rvagg/through2)モジュールで受け取って暗号化。
その後にまた復号化して復号化して、writableStreamに書き込む。
writableStreamに書き込みが終わると、全て複合されているので、結果を表示して終了。

```js
var ursa = require('ursa');
var fs = require('fs');
var userHome = process.env.HOME;

// 公開鍵
var keyPub = fs.readFileSync(userHome + '/.ssh/id_rsa.pub', 'utf8');
var pubKey = ursa.openSshPublicKey(keyPub);

// 秘密鍵
var keyPrivate = fs.readFileSync(userHome + '/.ssh/id_rsa', 'utf8');
var privateKey = ursa.createPrivateKey(keyPrivate);

var streamBuffers = require('stream-buffers'),
    through2 = require('through2');

// 平文
var text = ["これは日本語のテキストです。",
           "公開鍵で200バイトずつ暗号化して、myWritableStreamBufferに書き込んでいます。",
           "パイプ処理が終わるとmyWritableStreamBufferにfinishイベントが発生します。"].join("\n");
           
// Readstreamで200バイトずつ読み込む
var myReadableStreamBuffer = new streamBuffers.ReadableStreamBuffer({
 frequency: 10,     // 10msずつ処理.
 chunkSize: 200     // 200バイトずつ読み込む
});
myReadableStreamBuffer.put(text);

// stop()メソッドを実行しないと readableStreamがendにならないので、
// writeableStreamもfinishにならない
myReadableStreamBuffer.stop();

// 書き込み先
var myWritableStreamBuffer = new streamBuffers.WritableStreamBuffer({
   initialSize: 200,    // 200バイトのバッファを確保
   incrementAmount: 200 // 200バイトずつバッファを追加
});


myReadableStreamBuffer
  .pipe(through2(function(chunk, enc, cb) {
    // 公開鍵で暗号化
    this.push(pubKey.encrypt(chunk));
    cb();
  }))
  .pipe(through2(function(chunk, enc, cb) {
    // 秘密鍵で復号化
    this.push(privateKey.decrypt(chunk));
    cb();
  }))
  .pipe(myWritableStreamBuffer);

myWritableStreamBuffer.on('finish', function() {
  console.log("---復号化されたテキスト");
  console.log(myWritableStreamBuffer.getContentsAsString('utf8'));
});

```

# id_rsa と id_rsa.pubが一致するか比較

id_rsaとid_rsa.pubが一致するかどうかだけをテストしたい場合はこうなります。

```js
var keyPub = fs.readFileSync(userHome + '/.ssh/id_rsa.pub', 'utf8');
var pubKey = ursa.openSshPublicKey(keyPub);
var keyPrivate = fs.readFileSync(userHome + '/.ssh/id_rsa', 'utf8');
var privateKey = ursa.createPrivateKey(keyPrivate, '<パスワード>');
console.log("match:" + ursa.matchingPublicKeys(privateKey, pubKey)); // true or false
```

# 共通鍵の作成

[公開鍵認証では公開鍵と秘密鍵で認証をしたあと、新しい公開鍵と秘密鍵を作成して相互に持つ](http://d.hatena.ne.jp/kasei_san/20120329/p1)必要があるのですが、
この共通鍵を作るのはこうなります。

```js
// Generates a pair key.
// https://github.com/quartzjer/ursa#ursagenerateprivatekeymodulusbits-exponent
var modulusBits = 2048;
var exponent = 65537;
var pairKey =  ursa.generatePrivateKey(modulusBits, exponent);

var publicKey = ursa.createPublicKey(pairKey.toPublicPem());
var privateKey = ursa.createPublicKey(pairKey.toPrivatePem());
```

# まとめ
Node.jsっていろいろな人が素晴らしいモジュールを作ってくれているので、便利ですね。
（玉石混淆ですけどね）