Creating Digital Signatures With Swift

Creating Digital Signatures With Swift

The
main purpose of a digital signature is to verify the integrity of
some information. For a simple example, let's say you had a file that
was transferred over the network and you want to check that the
entire file was transferred correctly. In that case, you would use a checksum.

“A
checksum is a small-sized datum derived from a block of digital data
for the purpose of detecting errors which may have been introduced
during its transmission or storage” — Wikipedia

How do
we derive that checksum? The best option is to use a hash. A hash function will take a variable amount of data and will output a signature of fixed length. For example, we could publish a file along with its hash online. When someone downloads the file, they can then run the same hash function on their version of the file and compare the result. If the hashes are the same then the copied or downloaded file is the same as the original. 

A hash is also a one-way function. Given the resulting output, there is no
computationally feasible way to reverse that hash to reveal what
the original input was. SHA, Secure Hash Algorithm, is a well-known standard that refers to a group of hash functions that have this property and certain others, which make them useful for digital signatures.

About SHA

SHA has undergone many iterations since it was first published. The
first and second iterations, SHA-0 and SHA-1, are now known to
have major weaknesses. They are no longer approved for security
implementations: they generally shouldn't be used for applications relying on
security. However, the SHA-2 family includes versions called SHA-256 and SHA-512, and these are
considered secure. "256" and "512" simply refer to the resulting number of
bits produced. For this tutorial, we are going to use SHA-512.

Note: Another older popular hash algorithm was MD5. It was also found to have significant flaws.

Using SHA is great for checking if data was accidentally corrupted,
but this doesn't prevent a malicious user from tampering with the
data. Given that a hash output is of a fixed size, all an attacker
needs to do is figure out which algorithm was used given the output
size, alter the data, and recompute the hash. What we need is
some secret information added to the mix when hashing the data so
that the attacker cannot recompute the hash without knowledge of
the secret. This is called a Hash Message Authentication Code (HMAC).

HMAC

HMAC can authenticate a piece of information or message to make sure that it originated from the correct sender and that the information has not been altered. A
common scenario is when you are talking to a server with a back-end API for your app. It may
be important to authenticate to ensure that only your app is allowed
to talk to the API. The API would have access control to a
specific resource, such as a /register_user endpoint. The client would need to sign its request to the /register_user endpoint in order to successfully use it.

When
signing a request, it is common practice to take selected parts of
the request, such as POST parameters and the URL, and join
them together into a string. Taking agreed-upon elements and putting
them in a particular order is called canonicalization. In
HMAC, the joined string is hashed along with the secret key to
produce the signature. Instead of calling it a hash, we use the term signature in the same way that a person's signature in real life is used to verify identity or integrity. The signature is added back to the
client's request as a request header (usually also named “Signature”). A
signature is sometimes called a message digest, but the two terms can
be used interchangeably.

Over on
the API side, the server repeats the process of joining the
strings and creating a signature. If the signatures match, it proves
that the app must have possession of the secret. This proves the
identity of the app. Since specific parameters of the request were also part of the string to be signed, it also guarantees the integrity of the request. It prevents an attacker from performing a man-in-the-middle attack, for example, and altering the request parameters to their liking.

In this code, the CCHmac function takes a parameter for the type of hash function to be used, along with two byte-strings and their lengths—the message and a secret key. For the best
security, use at least a 256-bit (32 byte) key generated from a
cryptographically secure random number generator. To
verify everything is working correctly on the other side, run the example and then input the
secret key and message on this remote server and verify that the output is
the same.

You can also add a timestamp header to the request and signing string to make the request more unique. This can
help the API weed out replay attacks. For example, the API could drop the
request if the timestamp is 10 minutes stale.

While
it's good to stick to using SHA versions that are secure, it turns out that many
of the vulnerabilities of the insecure SHA versions do not apply to
HMAC. For this reason, you may see SHA1 being used in production code. However, from a
public relations standpoint, it may look bad if you have to explain
why, cryptographically speaking, it is okay to use SHA1 in this context. Many of
the weaknesses of SHA1 are due to what are called collision attacks.
Code auditors or security researchers may expect your code to be
collision resistant, regardless of the context. Also, if you write
modular code where you can swap out the signing function for a
different one in the future, you might forget to update the insecure hash
functions. Therefore, we will still stick to SHA-512 as our algorithm
of choice.

The HMAC CPU operations are
fast, but one disadvantage is the problem of key exchange. How do we
let each other know what the secret key is without it being
intercepted? For example, maybe your API will need to dynamically add
or remove multiple apps or platforms from a whitelist. In this
scenario, apps would be required to register, and the secret would
need to be passed to the app upon successful registration. You could send the
key over HTTPS and use SSL pinning, but even then there is always a worry that somehow
the key is stolen during the exchange. The
solution to the problem of key exchange is to generate a key that
doesn't ever need to leave the device in the first place. This can be
accomplished using Public Key Cryptography, and a very popular and accepted standard is RSA.

RSA

RSA
stands for Rivest-Shamir-Adleman (the authors of the cryptosystem).
It involves taking advantage of the difficulty of factoring the
product of two very large prime numbers. RSA can be used for
encryption or authentication, although for this example we are going
to be using it just for authentication. RSA generates two keys, a
public and a private, which we can accomplish using the SecKeyGeneratePair function. When used for authentication, the private key
is used to create the signature, while the public key verifies the
signature. Given a public key, it is computationally unfeasible to
derive the private key.

The
next example demonstrates what Apple and all the popular
gaming console companies use when distributing their software.
Let's say your company creates and delivers a file periodically that
users will drag into the file sharing portion of your app in
iTunes
. You want to make sure the files you send out are never
tampered with before being parsed in the app. Your company will hold
onto and guard the private key which it uses to sign the files. In
the bundle of the app is a copy of the public key used to verify the
file. Given that the private key is never transmitted or included in
the app, there is no way for a malicious user to be able to sign
their own versions of the files (apart from breaking into the company and stealing the
private key).

We will use SecKeyRawSign to sign the file. It
would be slow to sign the entire contents of the file using RSA, so the hash of the file is
signed instead. Additionally, the data passed to RSA should also be hashed
before signing because of some security weaknesses.

In this code, we
used the CC_SHA512 function to specify SHA-512 again. (RSA, unlike HMAC, becomes insecure if the
underlying hash function is insecure.) We are also using 4096 as the key
size, which is set by the kSecAttrKeySizeInBits parameter. 2048 is the minimum recommended size.
This is to prevent a powerful network of computer systems cracking
the RSA key (by cracking I mean factoring the RSA key—also known as factorization
of a public modulus
). The RSA group has estimated that 2048-bit
keys could become crackable some time before 2030. If you want your
data to be safe beyond that time then it's a good idea to choose a
higher key size like 4096.

The generated keys are in the form of SecKey objects. An
issue with Apple's implementation of SecKey is that it does not
include all of the essential information that makes up a public key, so it's not a valid DER-encoded X.509 certificate. Adding the missing information back into
the format for an iOS or OS X app, even server-side platforms such as PHP, requires some work and involves working in a format
known as ASN.1. Fortunately, this was fixed in iOS 10 with
new SecKey functions for generating, exporting, and importing keys. 

The code below shows you the other side of the communication—the class that accepts a public key via SecKeyCreateWithData to verify files using the SecKeyRawVerify function.

You could try this out and verify that it works using a simple test like the following:

There is one downside to RSA—key generation
is slow! The
time to generate the keys is dependent on the size of the key. On
newer devices a 4096 bit key takes only a few seconds, but if you run this
code on an iPod Touch 4th generation, it may take about
a minute. This is
fine if you are just generating the keys a few times on a computer, but
what happens when we need to generate keys frequently on a mobile
device? We can't just lower the key size because that downgrades the
security. 

So what's the solution? Well, Elliptic Curve Cryptography
(ECC) is an up-and-coming approach—a new set of algorithms based on
elliptic curves over finite
fields. ECC keys are much smaller in size and faster to generate than RSA keys. A
key of only 256-bits offers a very strong level of security! To take advantage of ECC, we don't
need to change a lot of code. We can sign our data using the same
SecKeyRawSign function and then adjust the parameters to use Elliptic
Curve Digital Signature Algorithm (ECDSA).

Tip:
For more RSA implementation ideas, you can check out the
SwiftyRSA helper
library, which is focused on
encryption as well as signing messages.

ECDSA

Imagine the following scenario:
a chat app lets users send private messages to each other, but you
want to make sure that an adversary has not changed the message on
its way to the other user. Let's see how you could secure their communication with cryptography. 

First, each user generates a keypair of public
and private keys on their mobile device. Their private keys are stored in memory and never
leave the device, while the public keys are transmitted to each
other. As before, the private key is used for signing the data being
sent out, while the public key is used for verifying. If an attacker
were to capture a public key during transit, all that could be done is to verify the integrity of the original message from the
sender. An attacker can't alter a message because they don't have the private key needed to reconstruct the signature.

There
is another pro to using ECDSA on iOS. We can make use of the fact that currently, elliptic curve keys are the only ones that can be stored in the secure enclave of the device. All other keys are stored in the
keychain which encrypts its items to the default storage area of the device. On devices that have one, the secure enclave sits separate from the processor, and key storage is implemented
in hardware without direct software access. The secure enclave can store a private key and operate on it to produce
output that is sent to your app without ever exposing the actual private
key by loading it into memory!

I will add support for creating the
ECDSA private key on the secure enclave by adding the kSecAttrTokenIDSecureEnclave option for the kSecAttrTokenID parameter. We can start this example with a User object that will generate a keypair upon initialization.

Next, we will create some helper and example functions. As an example, the class will allow a user to initiate a conversation and send a message. Of course, in your app, you would configure this to include your specific networking setup.

Next, we will do the actual signing and verification. ECDSA,
unlike RSA, does not need to be hashed prior to signing. However, if
you wanted to have a function where the algorithm can be easily
swapped without making many changes, then it's perfectly fine to
continue to hash the data before signing.

This
verifies the message, as well as the “identify” of a specific
user since only that user has possession of their private key. 

This
doesn't mean that we're connecting the key with who the user is in real life—the problem
of matching a public key to a specific user is another domain. While
the solutions are out of the scope of this tutorial, popular secure chat
apps such as Signal and Telegram allow users to verify a fingerprint
or number via a secondary communication channel. Similarly, Pidgin offers a question and answer scheme
whereby you ask a question that only the user should know. These solutions
open a whole world of debate on what the best approach should be.

However, our cryptographic solution does verify that the message can only have been sent by someone who is in possession of a specific private key.

Let's run a simple test of our example:

OAuth and SSO

Often when working with third-party services, you will notice other high-level terms used for authentication, such as OAuth and SSO. While this tutorial is about creating a
signature, I will briefly explain what the other terms mean.

OAuth
is a protocol for authentication and authorization. It acts as an
intermediary to use someone's account for third-party services and
aims to solve the problem of selectively authorizing access to your
data. If you log in to service X via Facebook, a screen asks you,
for example, if service X is allowed to access your Facebook photos.
It accomplishes this by providing a token without revealing the
user's password.

Single
sign-on, or SSO, describes the flow where an authenticated user can
use their same login credentials to access multiple services. An
example of this is how your Gmail account works to log in to YouTube. If
you had several different services at your company, you may not want
to create separate user accounts for all of the different services.

Conclusion

In this tutorial, you saw how to create signatures using the most popular standards. Now
that we have covered all the main concepts, let's recap!

  • Use
    HMAC when you need speed and are sure that the secret key can be
    exchanged securely.
  • If the keys have to travel across a network, it's
    better to use RSA or ECDSA.
  • RSA is
    still the most popular standard. Its verification step is quite
    fast. Use RSA if the rest of your team is already familiar with or
    using the standard.
  • If you need to constantly generate keys on a slow
    device, however, use ECDSA. While the ECDSA verification is a tad
    slower than RSA verification, that doesn't compare to the many
    seconds saved over RSA for key generation.

So that's it for digital
signatures in Swift. If you have any questions, feel free to drop me a
line in the comments section, and in the meantime check out some of our other tutorials on data security and app development in Swift!

Source: Tuts Plus

About the Author