6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOSAdvent Calendar 2019

Day 19

iOSで使う暗号化処理をユニットテストする方法を考える

Posted at

iOSアプリでちょっとした暗号化処理が必要な場合があります。たとえば何らかのハッシュ値をサーバーとの認証処理に使うといった例が考えられます。
ちょっとした処理なのでこんな感じで自前で実装することも多いのではないでしょうか?

import Foundation
import CommonCrypto

final class MyCrypto {
    func sha256(_ string: String) -> String {
        var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))

        let data = string.data(using: .utf8)!
        data.withUnsafeBytes { pointer -> Void in
            CC_SHA256(pointer.baseAddress, CC_LONG(data.count), &hash)
        }
        return Data(hash).base64EncodedString()
    }
}

実装したはいいですが、ユニットテストしたいですよね?僕はしたいです。
この記事では上記のような処理をXCTestでユニットテストする方法について考えてみました。

以下では、僕のやってみた方法を書きましたが、もっと他に良い方法があれば教えていただければ幸いです。

ユニットテストの方針

自前の実装を「信頼できる既存の実装」と比べて結果が同じならばきちんと実装できていると考えて良いでしょう。

let hash = 自前実装
let expectedHash = 信頼できる既存の実装

XCTAssetEqual(hash, expectedHash)

というユニットテストを書きたいわけです。

幸い、macOSには opensslコマンドが標準でインストールされています。下の例のように、opensslコマンドで大抵の暗号化処理を行うことができるので、これを「信頼できる既存の実装」としましょう。

opensslコマンドでsha256
$ printf 'hoge' | openssl dgst -binary -sha256 | openssl base64
7LZm13hyXslzBwRNZCv00WCqu3b1bABpxx6iWx6SaCU=

ユニットテストの実装

先ほどの方針のようにXCTestを書いていくことにします。

Step 1: Build Phaseでopensslコマンドを使ってテスト用データを用意

ユニットテストのBuild Phaseで任意のスクリプトを実行できます。ここから設定しましょう。

set -eu

swiftfilename="$SRCROOT/$TARGETNAME/HashTestSpec.generated.swift"

input='foo'
expected=$(printf "$input" | openssl dgst -binary -sha256 | openssl base64)

cat << EOF > "$swiftfilename"
enum HashTestSpec {
    static var input: String { "$input" }
    static var expected: String { "$expected" }
}
EOF

こうすると、以下のようなSwiftファイルができます。

enum HashTestSpec {
    static var input: String { "foo" }
    static var expected: String { "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=" }
}

Step 2: XCTestで値を比較

あとはXCTAssertEqualで比較します。

class CryptoTests: XCTestCase {
    func testSHA256() {
        let input = HashTestSpec.input
        let hash = MyCrypto().sha256(input)
        let expected = HashTestSpec.expected
        XCTAssertEqual(hash, expected)
    }
}

サンプルプロジェクト

Xcode 11.3 でプロジェクトを開いてユニットテストを実行してみてください(Command + U)。
https://github.com/taka1068/CryptoUnitTestExample

CryptoSwiftのテストを読む

以上が僕のたどりついたユニットテスト手法です。最後にサードパーティライブラリのユニットテストを読んでみます。
個人的に、著名ライブラリのユニットテストを読むと勉強になることが多いと思います。

Swift製の暗号化ライブラリといえば CryptoSwift が思い当たります。

CryptoSwiftのユニットテストは以下のようになっています。(見やすいように改行してあります。)
https://github.com/krzyzanowskim/CryptoSwift/blob/master/Tests/CryptoSwiftTests/DigestTests.swift

func testSHA3() {
    XCTAssertEqual(
        SHA3(variant: .sha224).calculate(for: Array<UInt8>(hex: "616263")).toHexString(), 
        "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
    )
    XCTAssertEqual(
        SHA3(variant: .sha256).calculate(for: Array<UInt8>(hex: "616263")).toHexString(), 
        "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
    )
    XCTAssertEqual(
        SHA3(variant: .sha384).calculate(for: Array<UInt8>(hex: "616263")).toHexString(),
        "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
    )

    // ....

どうやら比較対象をユニットテストにベタ書きしているみたいです。この比較対象は、 http://www.di-mgt.com.au/sha_testvectors.html から取得しているようです(ソースのヘッダ部分にリンクがあります)。

なるほど、信頼できるソースからコピーしてきたようです。これもありですね。

6
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?