rsa公開鍵を用いてiOSクライアントで暗号化しGoのサーバサイドで復号する


# はじめに

rsa暗号化についてはいくつかオプションがあります。
今回は選択暗号文攻撃に強いRSA-OAEPに絞った調査を行っています。

# 秘密鍵と公開鍵の準備

以下のコマンドで作成します

```
openssl genrsa 2048 > private_key.pem
openssl rsa -pubout < private_key.pem > public_key.key
```

# Goだけで暗号化、復号化を行う

OAEPについてはいかに情報があります。
https://ja.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding

Goは割と素直な感じで

![oaep](https://upload.wikimedia.org/wikipedia/commons/1/18/Oaep-diagram-20080305.png)

上記の概念図おけるG,Hのハッシュアルゴリズムの指定とrに当たるラベルの指定ができます。

以下インターフェースの抜粋箇所です

```go
 //暗号化
 ciphertext, err := rsa.EncryptOAEP(sha256.New()/*ハッシュアルゴリズム*/, rng/*ランダム値生成機*/, &privateKey.PublicKey, secretMessage, label/*ラベル*/)
// 復号
plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, privateKey, ciphertext, label)

```

goだけで閉じているならとっても楽なんですが、、、、


```go

package rsa_test

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"testing"
)

func TestEncDec(t *testing.T) {
	privateKey, err := readRsaPrivateKey("private_key.pem")
	if err != nil {
		t.Errorf("sorry. can't read privatekey err=%s", err.Error())
	}

	expectMessage := "hello rsa"
	secretMessage := []byte(expectMessage)
	label := []byte("label")

	t.Logf("secretMessage = %s", secretMessage)

	// crypto/rand.Reader is a good source of entropy for randomizing the
	// encryption function.
	rng := rand.Reader

	// 暗号化
	ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, &privateKey.PublicKey, secretMessage, label)
	if err != nil {
		t.Errorf("Error from encryption: %s\n", err)
		return
	}

	t.Logf("ciphertext = %s", ciphertext)
	// 復号
	plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, privateKey, ciphertext, label)
	if err != nil {
		t.Errorf("Error from decryption: %s\n", err)
		return
	}

	t.Logf("Plaintext: %s\n", string(plaintext))

	if expectMessage != string(plaintext) {
		t.Fatalf("expect %s, but %s", expectMessage, string(plaintext))
	}

}

func readRsaPrivateKey(pemFile string) (*rsa.PrivateKey, error) {
	bytes, err := ioutil.ReadFile(pemFile)
	if err != nil {
		return nil, err
	}

	block, _ := pem.Decode(bytes)
	if block == nil {
		return nil, errors.New("invalid private key data")
	}

	var key *rsa.PrivateKey
	if block.Type == "RSA PRIVATE KEY" {
		key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
	} else if block.Type == "PRIVATE KEY" {
		keyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
		var ok bool
		key, ok = keyInterface.(*rsa.PrivateKey)
		if !ok {
			return nil, errors.New("not RSA private key")
		}
	} else {
		return nil, fmt.Errorf("invalid private key type : %s", block.Type)
	}

	key.Precompute()

	if err := key.Validate(); err != nil {
		return nil, err
	}

	return key, nil
}


```

参考:
http://increment.hatenablog.com/entry/2017/08/25/223915


実行結果はこんな感じです

```
$ go test -v -timeout 30s github.com/m0a-mystudy/rsa/go -run ^(TestEncDec)$

=== RUN   TestEncDec
--- PASS: TestEncDec (0.00s)
    enc_dec_test.go:25: secretMessage = hello rsa
    enc_dec_test.go:38: ciphertext = ����*����η<省略>                                               
    enc_dec_test.go:46: Plaintext: hello rsa
PASS
ok  	github.com/m0a-mystudy/rsa/go	(cached)
```

なんの問題もなく暗号化と復号が成功しています。まぁ当たり前です。
では暗号化の部分をiOSに担当していただきましょう。


# iOSで暗号化しGoで復号する

今回の主題です。微妙にインターフェースが異なったり、用いている変数名に統一感がないため、
動かすまでに苦労しています。

iOSで現在rsa暗号化をサポートしているライブラリはgithubで探しますがめぼしいのは２つほどです。

* [TakeScoop/SwiftyRSA](https://github.com/TakeScoop/SwiftyRSA) 601 star
* [soyersoyer/SwCrypt](https://github.com/soyersoyer/SwCrypt) 483 star

参考:https://github.com/topics/rsa

上記２つのライブラリについて実際に暗号化(iOS)と復号(Go)を行ってみます。

## SwiftyRSAの場合


インターフェースを確認すると

```swift
        let clear = try! ClearMessage(string: <暗号化対象文字列>, using: .utf8)
        let encrypted = try! clear.encrypted(with: <publicKey>, padding: <パディングアルゴリズム>)

```

歴史的経緯なのかhashアルゴリズムとラベルの指定をサポートしておらず、
sha1,ラベルは空で固定のようです。

どなたか経緯を教えていただければ幸いです。


### iOSで暗号化

```swift

import XCTest
import SwiftyRSA

@testable import rsa_test

class rsa_testTests: XCTestCase {
    var publicKey: Data = Data()
    var publicKeyPem: String = ""
    
    override func setUp() {
        let publicKeyPath = Bundle.main.path(forResource: "public_key", ofType: "pem") ?? ""
        self.publicKeyPem = try! String(contentsOfFile: publicKeyPath)
    }
    
    func testEncOAEP_SwiftyRSA() {
        let publicKey = try! PublicKey.publicKeys(pemEncoded: self.publicKeyPem)[0]
        let expectString = "hello ios rsa"
        let clear = try! ClearMessage(string: expectString, using: .utf8)
        let encrypted = try! clear.encrypted(with: publicKey, padding: .OAEP)
        print("SwiftyRSA chiperText[\(encrypted.base64String)]")
    }
    
}

```

出力例はこちら

```
SwiftyRSA chiperText[eCezV7gNUMHWNmS2ayePXIe64b7Rk86Is98/uZgPv1g3JLrQieUArhM1gAaJyZaK7Yf/BOqe8/CQ7O2ybKIZDu3HCbwEPhYjoKpNeXVNaIRdiLJa3onmFM5mQJdU+Zr7cH6GOvmx29ZKR4mC6p8BrRqZnh6s3D/5PcGrKqrgh8X9ry9F1ZYptDf36ZO4YdBNKTYy9mVoYqGFlmmiZWAQvvCufnte+PCr5Gxw6DsADYt1ByiDeron+hZMzK9PPiZup8fquAXdXup+PXqlJIvXFyrCneocrsBY1YA8xTfJlxF/ZHUPNZaSb0QiM6PJj3xsU3vg5ZUT/OcgTYhI7lW5SQ==]
```

### Goで復号

```go
package rsa_test

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"testing"
)

func TestDecforSwiftyRSA(t *testing.T) {
	base64Text := "lqEicpwE0xPg9iXdn2xSQLeoIEkwvKdxGBMrjwHxW2S5x7IuhrWCnWnFZ1w0nd21nCuZfveW29nCzekvJEBji8W+HcbwQUapIcRoENp6+IkcjISnOR9hR5ZOJBUNP7X0eLniFHuqPXuySWuzGXJIfP2P8iBwFEC0AvUTGUfpXdYEoRP5uEExHBdxw/WywtjocGkgz+sbmBzgCdN+BmAas8h/RdsYI2D83VyvG492Hp45SR+vgoyv4TbqcWZqdPC6T4ZFurQvZKlMKT5Xfhe4WTQUVL1fKvFWGkxXhIesKmxZpvIfKqLF7ZuGs13RJaIDvF6i71fvmF7rN2fvE/iuoA=="
	expectMessage := "hello ios rsa"

	privateKey, err := readRsaPrivateKey("private_key.pem")
	if err != nil {
		t.Errorf("sorry. can't read privatekey err=%s", err.Error())
	}
	rng := rand.Reader

	ciphertext, _ := base64.StdEncoding.DecodeString(base64Text)

	t.Logf("ciphertext = %s", ciphertext)
	// 復号
	plaintext, err := rsa.DecryptOAEP(sha1.New(), rng, privateKey, ciphertext, nil)
	if err != nil {
		t.Errorf("Error from decryption: %s\n", err)
		return
	}

	t.Logf("Plaintext: %s\n", string(plaintext))

	if expectMessage != string(plaintext) {
		t.Fatalf("expect %s, but %s", expectMessage, string(plaintext))
	}

}

func readRsaPrivateKey(pemFile string) (*rsa.PrivateKey, error) {
	bytes, err := ioutil.ReadFile(pemFile)
	if err != nil {
		return nil, err
	}

	block, _ := pem.Decode(bytes)
	if block == nil {
		return nil, errors.New("invalid private key data")
	}

	var key *rsa.PrivateKey
	if block.Type == "RSA PRIVATE KEY" {
		key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
	} else if block.Type == "PRIVATE KEY" {
		keyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
		var ok bool
		key, ok = keyInterface.(*rsa.PrivateKey)
		if !ok {
			return nil, errors.New("not RSA private key")
		}
	} else {
		return nil, fmt.Errorf("invalid private key type : %s", block.Type)
	}

	key.Precompute()

	if err := key.Validate(); err != nil {
		return nil, err
	}

	return key, nil
}

```

出力結果

```
$ go test -v  github.com/m0a-mystudy/rsa/go -run ^(TestDecforSwiftyRSA)$
=== RUN   TestDecforSwiftyRSA
--- PASS: TestDecforSwiftyRSA (0.00s)
    enc_dec_test.go:37: Plaintext: hello ios rsa
PASS
ok  	github.com/m0a-mystudy/rsa/go	0.015s
```

復号成功です。


## SwCryptの場合

### iOSで暗号化

tag == ラベルですね。統一してほしい、、

```swift

import XCTest
import SwCrypt

@testable import rsa_test

class rsa_testTests: XCTestCase {
    var publicKey: Data = Data()
    var publicKeyPem: String = ""
    
    override func setUp() {
        let publicKeyPath = Bundle.main.path(forResource: "public_key", ofType: "pem") ?? ""
        self.publicKeyPem = try! String(contentsOfFile: publicKeyPath)
        self.publicKey = try! SwKeyConvert.PublicKey.pemToPKCS1DER(self.publicKeyPem)
    }

    func testEncOAEP_SwCrypt() {
        let expectString = "hello ios rsa"
        guard let data = expectString.data(using: .utf8) else {
            return
        }
        
        guard let tag = "label".data(using: .utf8) else {
            return
        }
        
        do {
        let chiperText = try CC.RSA.encrypt(data, derKey: self.publicKey, tag: tag, padding: .oaep, digest: .sha256)
            print("SwCrypt chiperText[\(chiperText.base64EncodedString())]")
        } catch {
            XCTAssertNotNil(error)
        }
    }
        
}


```

上記の出力結果抜粋

```
SwCrypt chiperText[N9QE8lG5A9LRRprfwkzAxoldJkNSJqEQ/gLAcNjMFbLgcGu2yffY3x91/DOCApsxtAU3I7GTCnk0TOTV5Y32zVqOE7S+GksCFFa7iDLsqYvQbKJZXcb8bTe4p93SPU+RBkH0r/H8NBTUDSvNtARcXntqWNwr0FNAW2HH/Ht+ZmL1pWMa0MmdXLc/+4S/KFjlip/b5neMw6EoQkfNNDz8i7+IHiIpz+vJ2ZFvpf8RGXLgUTMGdUrQfv+XyLZZAnYny5a8HzuMbEcSunez0G7T25NGj6ubJJ2zalLACV1GysolOAJFn6YD6jSukDGQmHKlL1JOkKhqUm9EZT6Mw7wBkQ==]
```

### Goで復号
生成された文字列をGoで復号してみます。

```go

func TestDecforSwCrypt(t *testing.T) {
	base64Text := "N9QE8lG5A9LRRprfwkzAxoldJkNSJqEQ/gLAcNjMFbLgcGu2yffY3x91/DOCApsxtAU3I7GTCnk0TOTV5Y32zVqOE7S+GksCFFa7iDLsqYvQbKJZXcb8bTe4p93SPU+RBkH0r/H8NBTUDSvNtARcXntqWNwr0FNAW2HH/Ht+ZmL1pWMa0MmdXLc/+4S/KFjlip/b5neMw6EoQkfNNDz8i7+IHiIpz+vJ2ZFvpf8RGXLgUTMGdUrQfv+XyLZZAnYny5a8HzuMbEcSunez0G7T25NGj6ubJJ2zalLACV1GysolOAJFn6YD6jSukDGQmHKlL1JOkKhqUm9EZT6Mw7wBkQ=="
	expectMessage := "hello ios rsa"

	privateKey, err := readRsaPrivateKey("private_key.pem")
	if err != nil {
		t.Errorf("sorry. can't read privatekey err=%s", err.Error())
	}
	rng := rand.Reader
	label := []byte("label")

	ciphertext, _ := base64.StdEncoding.DecodeString(base64Text)

	t.Logf("ciphertext = %s", ciphertext)
	// 復号
	plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, privateKey, ciphertext, label)
	if err != nil {
		t.Errorf("Error from decryption: %s\n", err)
		return
	}

	t.Logf("Plaintext: %s\n", string(plaintext))

	if expectMessage != string(plaintext) {
		t.Fatalf("expect %s, but %s", expectMessage, string(plaintext))
	}

}

```

出力結果

```
$ go test -v  github.com/m0a-mystudy/rsa/go -run ^(TestDecforiOS)$

=== RUN   TestDecforiOS
--- PASS: TestDecforiOS (0.00s)
    enc_dec_test.go:29: ciphertext = Dٽs隁������<省略>
    enc_dec_test.go:37: Plaintext: hello ios rsa
PASS
ok  	github.com/m0a-mystudy/rsa/go	0.015s
```

無事復号



# 最後に

テストとして書いたコードはこちらにおいておきます
https://github.com/m0a-mystudy/rsa