Categories
Explainer Technical

ECDSA signatures with node.js and Swift

In the process of de-risking some business model questions for Digamo, I went looking for an open source project that would allow us to generate and verify license keys. We’re not sure what our business model will be there, but understanding the technical costs of licensing is important to making decisions.

While this is a problem that has been solved, I didn’t find anything especially modern, well-maintained, or documented—and nothing in Swift or Node, the languages I’m using for everything else right now..

Some research did suggest that asymmetric cryptography was the way to go, using a private key to sign a registration string, and a public key distributed with the app to verify the signature. This was also the approach used in the older projects that tried to solve this problem for Mac apps.

Still, I found precious little documentation or tutorials to walk me through the implementation. All the signing examples took place within the same context, rather than between client and server. Here’s an approach that appears to be working, using Node and a Swift Mac app.

Try the demo

Generate a signed message file here: https://eccsign-server.glitch.me

Download, build and run the macOS verification app here: https://github.com/daniloc/eccsignaturetest

Once the app is running, you can double-click the signed message files from the Glitch app to automatically load them. File free to tamper with the content of the plaintext field by opening a file in your favorite text editor, then try to verify again. Tampering should cause the verification step to fail.

The public and private keys are included for your inspection and experimentation, as a “known-good” configuration. In your own projects you should use care to ensure your private key is not distributed.

Generate encryption keys

Per the instructions from IBM’s BlueECC project, here’s how you get started:

On macOS you can install OpenSSL using brew:

brew install openssl

Once you have installed OpenSSL, create your private key:

openssl ecparam -name prime256v1 -genkey -noout -out ec256priv.pem

Using the private key, create your public key:

openssl ec -in ec256priv.pem -pubout -out ec256pub.pem

It seems you want to use the prime256v1 curve—other algorithms have some cross-platform issues.

This will leave you with a private and public key. The private key goes on your server to generate signatures. The public key can be distributed with your app to verify those signatures.

Signing with Node.js

The npm module EC-Key will make it easy to load your key:

let ECKey = require("ec-key");

let pem = fs.readFileSync("./privatekey.pem");
let eccPrivateKey = new ECKey(pem, "pem")

Here my implementation gets a little clunky—there may be better ways to do this, but at least it seems pretty flexible. Create a JavaScript object with whatever keys and content you want:

var objectToSign = {}
objectToSign.message = message
let date = new Date().toJSON()
objectToSign.date = date

Convert that into a JSON string:

let outputString = JSON.stringify(objectToSign)

Then, create a signature from that string:

let signature = eccPrivateKey.createSign("SHA256").update(outputString).sign("base64")

And pack the plaintext string and signature into a second object:

let outputObject = {}
outputObject.plaintext = outputString
outputObject.signatureBase64 = signature

You can then convert the output to JSON and have the user download the file.

See the whole thing in action with this Glitch project.

Verification in Swift

Add the BlueECC package to your project. Install manually, or use the Swift Package Manager. In Xcode, choose File > Swift Packages > Add Package Dependency…

Search for “CryptorECC,” and pick “BlueECC.”

Add your public key file to your project, and import CryptorECC
to the file where you’re working. Then you can grab the public key like this:

let filepath = Bundle.main.path(forResource: "ec256pub", ofType: "pem")!

let keyString = try? String(contentsOfFile: filepath)

let publicKey = try ECPublicKey(key: keyString)

With the public key loaded from your bundle into memory, you can now verify a signature with it:

let signature = try ECSignature(asn1: Data(base64Encoded: signatureBase64)!)

(signatureBase64 is the output from createSign() above)

let verified = signature.verify(plaintext: plaintext, using: publicKey)

The constant verified will tell you if the plaintext and signature match.

Here’s a Mac app you can build and run to see this in action.

Feedback

Is this a good approach? See anywhere it could work better? Drop me a line: danilo@futurefluent.com.