Elliptic Curve Digital Signature Algorithm(ECDSA)是一個常用於 digital signature 的 digital signature algorithm(DSA)。本文章將介紹,如何在 Android 和 iOS 中使用 ECDSA 來 signing 和 verifying。
Table of Contents
Digital Signature
我們有時需要驗證從 server 端接收的資料,是否真的是由 server 端發送出來的,還是有其他人假冒 server 端發出資料給我們。為了要解決這樣的問題,server 端在發送資料時,會另外發送一串驗證碼。當 client 端收到資料與驗證碼後,client 端會使用特定的演算法將資料轉換成一個字串,再將此字串與驗證碼比對。如果相同的話,則我們可以確定資料是由 server 端發送出來的,並宣稱資料是安全的。
Digital signature 基本上就是定義上述的流程,並且提供可靠的演算法,以防止其他人偽照驗證碼。Server 端產生驗證碼的流程,稱為簽名(signing)。首先,server 端要先將資料用特定的 hash 函式處理過,這會產生出一個比較短且固定長度的 hash 值。然後,再將此 hash 值和一個 private key 輸入到特定的 DSA,最後它會產生一串驗證碼,稱為簽章(signature)。
Client 端接收到 server 端傳過來的資料和 signature。它會將資料用特定的 hash 函式處理過,並得到一個 hash 值。將 signature 和一個 public key 輸入到特定的 DSA,它會產生一個 hash 值。最後,比較這兩個 hash 值是否相同。這段流程稱為驗證(verifying)。
ECDSA Signature
ECDSA是一個近年來常用的 DSA。在使用 ECDSA 時,server 與 client 端要使用相同的 curve parameters。在本文章的範例中,我們選用 secp256r1。SEC secp256r1、NIST P-256、和 ANSI X9.64 ansix9p256r1 都是的相同。只是他們是由不同的機構定義的,請參照這裡。
當選擇使用 ECDSA 與 secp256r1,則 signing 的流程則會如下:
而,verifying 的流程如下:
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。不然,最好使用第三方套件比較保險。