// swift-tools-version:6.2import PackageDescription let package = Package( name: "howto", platforms: [.macOS(.v15)], dependencies: [ .package(url: "https://github.com/apple/swift-crypto.git", exact: "5.0.3"), ], targets: [ .executableTarget(name: "sign", dependencies: [.product(name: "Crypto", package: "swift-crypto")]), ]) import Cryptoimport Foundation // "BEGIN PRIVATE KEY" (PKCS#8) or "BEGIN EC PRIVATE KEY" (SEC 1)let privateKey = try P256.Signing.PrivateKey(pemRepresentation: String(contentsOfFile: "key.pem", encoding: .utf8))let message = try Data(contentsOf: URL(fileURLWithPath: "message.txt")) let signature = try privateKey.signature(for: message) // SHA-256, then ECDSA with a fresh nonce try signature.derRepresentation.write(to: URL(fileURLWithPath: "sig.der")) // for OpenSSL, Java, Gotry signature.rawRepresentation.write(to: URL(fileURLWithPath: "sig.raw")) // r‖s for JWS, WebCryptoprint("DER: \(signature.derRepresentation.count) bytes, raw: \(signature.rawRepresentation.count) bytes")print("base64 (DER): \(signature.derRepresentation.base64EncodedString())") How it works
P256.Signing.PrivateKey(pemRepresentation:)reads PKCS#8 (BEGIN PRIVATE KEY) and SEC 1 (BEGIN EC PRIVATE KEY) files.signature(for:)hashes the data with SHA-256 and signs; passing aSHA256Digestinstead signs a digest you computed yourself.derRepresentationis ASN.1 DER — the run verifies it withopenssl dgst -verify— andrawRepresentationis the fixed 64-byter‖s.base64EncodedString()on the DER bytes is the form to paste into JSON, HTTP headers or the signature verifier.
Gotchas
- Every call returns different bytes: CryptoKit randomizes the nonce, and it offers no RFC 6979 deterministic mode. Tests must verify signatures rather than compare them.
- CryptoKit does not normalize
s, so about half the signatures are high-S. Generic verifiers accept them; Bitcoin-style ones (andk256in Rust) reject them. - On Apple platforms, a
SecureEnclave.P256.Signing.PrivateKeysigns the same way but has no exportable PEM — itsdataRepresentationis an opaque handle valid only on that device. - Passing
message.utf8of aStringand the bytes read from a file can differ (a trailing newline added by an editor is enough). Sign exactly the bytes the verifier will hash.