Skip to content
ecdsa.com

Verify a signature · Swift

How to verify an ECDSA signature in Swift

To verify an ECDSA signature in Swift, use CryptoKit's API: P256.Signing.PublicKey loads the key from PEM, and isValidSignature checks a signature you construct from either encoding — derRepresentation for OpenSSL-style DER, rawRepresentation for the r‖s of JWS and WebCrypto. On Linux the same code runs with Apple's swift-crypto package.

Tested with swift-crypto 5.0.3 (Swift 6.4.1 on Ubuntu 24.04 (BoringSSL backend)).

Package.swift — swift-crypto for Linux (Apple platforms ship CryptoKit)
// swift-tools-version:6.2
import PackageDescription
let package = Package(
name: "howto",
platforms: [.macOS(.v15)],
dependencies: [
// CryptoKit's API on Linux; on Apple platforms "import CryptoKit" needs no package
.package(url: "https://github.com/apple/swift-crypto.git", exact: "5.0.3"),
],
targets: [
.executableTarget(name: "verify", dependencies: [.product(name: "Crypto", package: "swift-crypto")]),
]
)
Sources/verify/main.swift — verify DER and raw signatures
import Crypto
import Foundation
let publicKey = try P256.Signing.PublicKey(pemRepresentation: String(contentsOfFile: "pub.pem", encoding: .utf8))
let message = try Data(contentsOf: URL(fileURLWithPath: "message.txt"))
// The signature type states the encoding: DER from OpenSSL and Java, raw r‖s from JWS and WebCrypto
let der = try P256.Signing.ECDSASignature(derRepresentation: Data(contentsOf: URL(fileURLWithPath: "sig.der")))
let raw = try P256.Signing.ECDSASignature(rawRepresentation: Data(contentsOf: URL(fileURLWithPath: "sig.raw")))
// isValidSignature(_:for:) hashes the data with SHA-256 itself
print(publicKey.isValidSignature(der, for: message) ? "valid" : "INVALID")
print(publicKey.isValidSignature(raw, for: message) ? "valid" : "INVALID")
print(publicKey.isValidSignature(der, for: Data("hello ECDSA".utf8)) ? "valid" : "INVALID")

How it works

  1. P256.Signing.PublicKey(pemRepresentation:) reads a BEGIN PUBLIC KEY block; derRepresentation:, x963Representation: (the 04 ‖ x ‖ y point) and rawRepresentation: cover the other common forms.
  2. ECDSASignature(derRepresentation:) and ECDSASignature(rawRepresentation:) make the encoding explicit — the most common ECDSA interop bug cannot slip through unnoticed.
  3. isValidSignature(_:for:) hashes the data with SHA-256 (the hash CryptoKit binds to P-256) and returns a Bool; a SHA256Digest can be passed instead of the data when you hashed it yourself.
  4. import Crypto is swift-crypto; on Apple platforms you can import CryptoKit instead, and the code is the same.

Gotchas

  • Constructors throw while verification does not: a signature of the wrong length throws incorrectParameterSize, DER bytes passed as raw throw too, but a well-formed signature that does not match just returns false.
  • Key types are per curve: a P-384 PEM loaded as P256.Signing.PublicKey throws incorrectParameterSize, and a certificate PEM throws invalidPEMDocument — extract the key with swift-certificates first.
  • CryptoKit accepts high-S signatures (both s and n − s verify). Bitcoin-style rules that require low-S must be checked separately.
  • The algorithm is fixed by the type — P256 always uses SHA-256 here. Verifying a P-256 signature made over SHA-384 needs the digest variant with a SHA384Digest.

Related recipes