ECDSA 的 Signing 與 Verifying 範例

Photo by Babak Fakhamzadeh on Unsplash
Photo by Babak Fakhamzadeh on Unsplash
Elliptic Curve Digital Signature Algorithm(ECDSA)是一個常用於 digital signature 的 digital signature algorithm(DSA)。本文章將介紹,如何在 Android 和 iOS 使用 ECDSA 來 signing 和 verifying。

Elliptic Curve Digital Signature Algorithm(ECDSA)是一個常用於 digital signature 的 digital signature algorithm(DSA)。本文章將介紹,如何在 Android 和 iOS 中使用 ECDSA 來 signing 和 verifying。

Android 完整程式碼可以在 下載。

iOS 完整程式碼可以在 下載。

Digital Signature

我們有時需要驗證從 server 端接收的資料,是否真的是由 server 端發送出來的,還是有其他人假冒 server 端發出資料給我們。為了要解決這樣的問題,server 端在發送資料時,會另外發送一串驗證碼。當 client 端收到資料與驗證碼後,client 端會使用特定的演算法將資料轉換成一個字串,再將此字串與驗證碼比對。如果相同的話,則我們可以確定資料是由 server 端發送出來的,並宣稱資料是安全的。

Digital signature 基本上就是定義上述的流程,並且提供可靠的演算法,以防止其他人偽照驗證碼。Server 端產生驗證碼的流程,稱為簽名(signing)。首先,server 端要先將資料用特定的 hash 函式處理過,這會產生出一個比較短且固定長度的 hash 值。然後,再將此 hash 值和一個 private key 輸入到特定的 DSA,最後它會產生一串驗證碼,稱為簽章(signature)。

Digital Signature - Signing.
Digital Signature – Signing.

Client 端接收到 server 端傳過來的資料和 signature。它會將資料用特定的 hash 函式處理過,並得到一個 hash 值。將 signature 和一個 public key 輸入到特定的 DSA,它會產生一個 hash 值。最後,比較這兩個 hash 值是否相同。這段流程稱為驗證(verifying)。

Digital Signature - Verifying.
Digital Signature – Verifying.

ECDSA Signature

ECDSA是一個近年來常用的 DSA。在使用 ECDSA 時,server 與 client 端要使用相同的 curve parameters。在本文章的範例中,我們選用 secp256r1。SEC secp256r1、NIST P-256、和 ANSI X9.64 ansix9p256r1 都是的相同。只是他們是由不同的機構定義的,請參照這裡

當選擇使用 ECDSA 與 secp256r1,則 signing 的流程則會如下:

ECDSA - Signing.
ECDSA – Signing.

而,verifying 的流程如下:

ECDSA - Verification.
ECDSA – Verification.

Kotlin 範例

生成 ECC Keys

import android.util.Base64
import java.security.KeyPairGenerator
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.spec.ECGenParameterSpec

var privateKeyBase64 = ""
var publicKeyBase64 = ""
var message = "Hello Wayne's Talk!"
var signatureBase64 = ""

fun generateKeys() {
    val keyPairGenerator = KeyPairGenerator.getInstance("EC")
    val ecGenParameterSpec = ECGenParameterSpec("secp256r1")
    keyPairGenerator.initialize(ecGenParameterSpec)

    val keyPair = keyPairGenerator.generateKeyPair()
    val privateKey = keyPair.private as ECPrivateKey
    val publicKey = keyPair.public as ECPublicKey

    privateKeyBase64 = Base64.encodeToString(privateKey.encoded, Base64.NO_WRAP)
    publicKeyBase64 = Base64.encodeToString(publicKey.encoded, Base64.NO_WRAP)

    println("Private Key: $privateKeyBase64")
    println("Private Key Format: ${privateKey.format}")
    println("Public Key: $publicKeyBase64")
    println("Public Key Format: ${publicKey.format}")
}

Signing

import android.util.Base64
import java.security.KeyFactory
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec

fun sign() {
    val privateKeyBytes = Base64.decode(privateKeyBase64, Base64.NO_WRAP);
    val pkcS8EncodedKeySpec = PKCS8EncodedKeySpec(privateKeyBytes)
    val keyFactory = KeyFactory.getInstance("EC")
    val privateKey = keyFactory.generatePrivate(pkcS8EncodedKeySpec)

    val signature = Signature.getInstance("SHA256withECDSA")
    signature.initSign(privateKey)

    signature.update(message.encodeToByteArray())

    val signatureBytes = signature.sign()
    signatureBase64 = Base64.encodeToString(signatureBytes, Base64.NO_WRAP)

    println("Message: $message")
    println("Signature: $signatureBase64")
}

Verifying

import android.util.Base64
import java.security.KeyFactory
import java.security.Signature
import java.security.spec.X509EncodedKeySpec

fun verify() {
    val publicKeyBytes = Base64.decode(publicKeyBase64, Base64.NO_WRAP)
    val x509EncodedKeySpec = X509EncodedKeySpec(publicKeyBytes)
    val keyFactory = KeyFactory.getInstance("EC")
    val publicKey = keyFactory.generatePublic(x509EncodedKeySpec)

    val signature = Signature.getInstance("SHA256withECDSA")
    signature.initVerify(publicKey)

    signature.update(message.encodeToByteArray())

    val signatureBytes = Base64.decode(signatureBase64, Base64.NO_WRAP)
    val isValid = signature.verify(signatureBytes)
    println("Signature is ${if (isValid) "valid" else "inValid"}")
}

Swift 範例

生成 ECC Keys

import CryptoKit

private var privateKeyBase64 = ""
private var publicKeyBase64 = ""
private var signagureBase64 = ""
private let message = "Hello Wayne's Talk!"

func generateKeys() {
    let privateKey = P256.Signing.PrivateKey()
    let publicKey = privateKey.publicKey
    
    privateKeyBase64 = privateKey.derRepresentation.base64EncodedString()
    publicKeyBase64 = publicKey.derRepresentation.base64EncodedString()
    
    print("Private Key: \(privateKeyBase64)")
    print("Public Key: \(publicKeyBase64)")
}

Signing

func sign() {
    let privateKeyData = Data(base64Encoded: privateKeyBase64)!
    let privateKey = try! P256.Signing.PrivateKey(derRepresentation: privateKeyData)
    let signature = try! privateKey.signature(for: message.data(using: .utf8)!)
    signatureBase64 = signature.derRepresentation.base64EncodedString()
    
    print("Signature: \(signatureBase64)")
}

Verifying

func verify() {
    let publicKeyData = Data(base64Encoded: publicKeyBase64)!
    let publicKey = try! P256.Signing.PublicKey(derRepresentation: publicKeyData)
    
    var sha256 = SHA256()
    sha256.update(data: message.data(using: .utf8)!)
    let messageData = sha256.finalize()
    
    let signatureData = Data(base64Encoded: signatureBase64)!
    let signature = try! P256.Signing.ECDSASignature(derRepresentation: signatureData)
    let isValid = publicKey.isValidSignature(signature, for: messageData)
    print("Signature is \(isValid ? "valid" : "invalid")")
}

結語

在相同長度的 key 之下,ECDSA 提供比 RSA 更高的安全等級。近年來 ECDSA 的使用比例升高很多。Android 和 iOS 沒有內建所有的 curve parameters。所以,在選用 curve parameters 時,必須要先確認裝置是否有內建該 curve parameters。不然,最好使用第三方套件比較保險。

參考

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Hans-Jurgen Mager on Unsplash
Read More

Kotlin Coroutine 教學

Kotlin 的 coroutine 是用來取代 thread。它不會阻塞 thread,而且還可以被取消。Coroutine core 會幫你管理 thread 的數量,讓你不需要自行管理,這也可以避免不小心建立過多的 thread。
Read More
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue 教學

GCD 提供有效率的並行處理,讓我們不需要直接管理多執行緒。它的 Dispatch Queues 可以循序地(serially)或是並行地(concurrently)執行任務。我們只需要將要並行的程式當作任務提交到 dispatch queues 就可以了。
Read More
Photo by Florinel Gorgan on Unsplash
Read More

如何製作一個 XCFramework

XCFramework 讓你可以將 iPhone、iPhone 模擬器等多的不同平台的二進位碼打包到一個可發佈的 .xcframework 檔。你只需要為你的 Framework 產生出一個 .xcframework 檔,就可以支援多種平台。
Read More