OpenSSL Nearly Ripped The Internet Apart … So Meet Google Tink

After Heartbleed there were some serious questions asked of OpenSSL. How could something so fundamental to the security of the Internet to…

Google Tink [here]

OpenSSL Nearly Ripped The Internet Apart … So Meet Google Tink

After Heartbleed there were some serious questions asked of OpenSSL. How could something so fundamental to the security of the Internet to allows to be run from old code? How could a library that was so important to trust on the Internet be maintained by just a few part-time people? How can we link our cryptography into Cloud-based key management?

The Legacy of OpenSSL

OpenSSL is commonly used by Linux-based servers to implement the SSL/TLS connection, and bugs are common. While zero-days and critical bugs are not as common as they were in the past, two were published recently.

OpenSSL was started with Eric A Young and Tim Hudson, in Dec 1998, who created the first version of OpenSSL (SSLeay — SSL Eric AYoung). This became Version 0.9.1. Eric finished his research and left to go off to go off to Cryptsoft (www.cryptsoft.com) and then joining RSA Security, where he is currently a Distinguished Engineer [here]. After Eric left, it was then left to Steve Marquess (from the US) and Stephen Henson (from the UK) to continue its development through the OpenSSL Software Foundation (OSF).

The code continued to grow with TLS support added to 1.0.1a on 14 March 2012. Unfortunately, a bug appeared on 1 Jan 2012 which implemented the Heartbeat protocol (RFC 6520), and which ended-up resulting in Heartbleed (Figure 1). The bug was actually introduced by the same person who wrote the RFC — Robin Seggleman, a German developer:

Figure 1: RFC 6520 led to Heartbleed

Every significant vulnerability gets a CVE number. Sometimes they are associated with a common name such as Heartbleed (CVE-2014–0160), BEAST (CVE-2011–3389), FREAK (CVE-2015–0204), and so on, but some, such as CVE-2016–2017, just doesn’t have a common name. The Heartbleed bug related to OpenSSL 1.0.1 and 1.0.2, and where an attacker could perform a MITM (Man-in-the-Middle) attack on AES encryption.

Anyone looking at the OpenSSL code at the time could see that it has no real focus on creating a consistent approach to code integration, and needed a redesign, especially in a world which is focused on mobile devices. And, so Google initially forked the OpenSSL code with BoringSSL, but then formally released Google Tink.

Google Tink

Google has formally released Tink Version 1.2.0 in August 2018, as a multi-language (Java, C++ and Objective C), cross-platform cryptographic library. Its main focus is to replace OpenSSL and where there are complex bindings and which were often focused on specific systems, such as for DLLs in Windows systems. While Google created Tink, it is open-source and has the aim to create simple APIs for cryptography functions and which should make the infrastructure more portable, secure and stable.

For Tink — based on BoringSSL and now at Version 1.7.0 [here] — the adoption has been good and is already integrated into AdMob, Google Pay, Google Assistant, and Firebase. It also integrates AEAD (Authenticated Encryption with Associated Data) methods and which integrates encryption keys, a hash function, and a message authentication code (MAC). Google, too, has analysed many cryptography weaknesses and have created code which addresses many of these problems. Tink also integrates with modern cloud-based key management systems such as Amazon KMS, Google Cloud KMS, Android Keystore, and iOS Keychain.

The minimal standards for AEAD include [RFC5116]:

  • The plaintext and associated data can have any length (from 0 to 2³² bytes).
  • Supports 80-bit authentication.
  • CCA2 security (adaptive chosen-ciphertext attack).

Google Tink: Symmetric key

AES is a symmetric key method, and where Bob and Alice have the same encryption key. In the following, Bob and Alice share an encryption key, and where Bob converts his plaintext into ciphertext, and then Alice converts the ciphertext back into plaintext using a shared secret key:

The problem with this setup is that the same plaintext will always result in the same ciphertext, so we typically add salt into the encryption process. We also need a way for Bob and Alice to generate the same secret key. This is either typically done through a key exchange method (such as with the Diffie-Hellman method) or by a KDF (Key Derivation Function). One of the most popular KDFs is PBKDF2 and which allows a password to be converted into an encryption key of a defined size:

Another mode of encryption is defined as AEAD (Authenticated Encryption with Additional Data), and where we can add some plaintext authenication data to the ciphering process, and where we need the same authentication data for the decrpytion process:

The size of the key is typically either 128 bits or 256 bits. AES (Advanced Encryption Standard) can be applied into three modes:

  • Block cipher. This can be implemented with CBC (Cipher Block Chaining) and which implements a block cipher. In the case of AES, the block size will be 16 bytes, and thus the ciphertext will be a multiple of the block size. As we use a block cipher, we need to pad the data for the cipher process, and then unpad the decrypted data. The most common method is PKCS7, and which pads with the value which equals the number of padding bytes.
  • AEAD (Authenticated Encryption with Additional Data). This can be implemented with GCM (Galois Counter Mode) and which implements with a stream cipher with the addition of additional data. This additional data can include something related to the ciphertext, such as with a session ID or TCP port. There is no padding required for the plaintext text, and thus the ciphertext will have the same length as the plaintext. As an alterative to use AEAD we can use HMAC for authenication, and which involves a signing key.
  • Stream cipher. This can be implemented with CFB (Cipher FeedBack) and which implements a stream cipher. As we use a stream cipher, we do not need to pad the data, and where the ciphertext size will be the same as the plaintext. Overall we use a simple XOR operation between the plaintext stream and a key stream (derived from the main encryption key). The decryption is performed by just XOR’ing the ciphertext stream with the same key stream. Common stream ciphers are ChaCha20 and XChaCha20.

In this case, we will use Tink with symmetric key encryption for the keys of 128-bit AES CTR with HMAC/SHA256, 128-bit AES GCM, 256-bit AES CTR HMAC/SHA256, 256-bit AES GCM, ChaCha20, and XChaCha20 (a longer nonce value) [here]:

package main
import (
"encoding/base64"
"fmt"
"os"
"strconv"
"github.com/google/tink/go/aead"
"github.com/google/tink/go/keyset"
"github.com/google/tink/go/insecurecleartextkeyset"
"github.com/golang/protobuf/proto"
)
func main() {
kh,_ :=keyset.NewHandle(aead.AES128CTRHMACSHA256KeyTemplate())
t:=1
m:="Message"
a:="aad"
argCount := len(os.Args[1:])
if argCount > 0 {
t,_ = strconv.Atoi(os.Args[1])
}
if argCount > 1 {
m = os.Args[2]
}
if argCount > 2 {
a = os.Args[3]
}
switch t {
case 1: kh,_ =keyset.NewHandle(aead.AES128CTRHMACSHA256KeyTemplate())
case 2:kh,_ =keyset.NewHandle(aead.AES128GCMKeyTemplate())
case 3:kh,_ =keyset.NewHandle(aead.AES256CTRHMACSHA256KeyTemplate())
case 4:kh,_ =keyset.NewHandle(aead.AES256GCMKeyTemplate())
case 5:kh,_ =keyset.NewHandle(aead.ChaCha20Poly1305KeyTemplate())
case 6:kh,_ =keyset.NewHandle(aead.XChaCha20Poly1305KeyTemplate())
}

k, _ := aead.New(kh)
msg := []byte(m)
aad := []byte(a)
ct, _ := k.Encrypt(msg, aad)

pt, _ := k.Decrypt(ct, aad)

fmt.Printf("Key: %v\n\n", kh)
fmt.Printf("Original plaintext: %s\n\n", a)
fmt.Printf("AAD: %s\n\n", msg)
fmt.Printf("Ciphertext: %s\n\n", base64.StdEncoding.EncodeToString(ct))
fmt.Printf("Decrypted Plaintext: %s\n", pt)
// Print key
ksw := &keyset.MemReaderWriter{}
_ = insecurecleartextkeyset.Write(kh, ksw)
ks, _ := proto.Marshal(ksw.Keyset)
fmt.Printf("\nRaw key: %s", base64.RawStdEncoding.EncodeToString(ks))
}

We use HMAC to provide an authentication of the message, rather than using the additional data (as in AEAD). A sample run proves the process:

Key: primary_key_id:2631268676 key_info:.type_url:"type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey" status:ENABLED key_id:2631268676 output_prefix_type:TINK . 
Original plaintext: qwerty
AAD: hello
Ciphertext: AZzV+UTxAwJUHEF0t4yXUDQN7mMnsaTQpuNo0sAQrd3AVsHQnOGn7PR/
Decrypted Plaintext: hello
Raw key: CMTy1+YJEo0BCoABCjh0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5jcnlwdG8udGluay5BZXNDdHJIbWFjQWVhZEtleRJCEhYSAggQGhAwWzfopZ6a8kD1+3QOOdzkGigSBAgDEBAaIHzdYOVzIF5FABwx4p42lvJrAeM0/1n+bm7ephAfDeJkGAEQARjE8tfmCSAB

Google Tink: MAC

One of the standard methods that we use in cryptography is to sign a message. For this we generate a signing key, and which is kept secret for a range of messages. This could relate to a single conversation between Bob and Alice, or for long-term communications between them.

HMAC is a message authentication code (MAC) that can be used to verify the integrity and authentication of a message. It involves hashing the message with a secret key and thus differs from standard hashing, which is purely a one-way function. As with any MAC, it can be used with a standard hash function, such as MD5 or SHA-1, which results in methods such as HMAC-MD5 or HMAC-SHA-1. Also, as with any hashing function, the strength depends on the quality of the hashing function, and the resulting number of hash code bits. Along with this, the number of bits in the secret key is a factor in the strength of the hash.

The figure below outlines the operation, where the message to be sent is converted with a secret key, and the hashing function, to an HMAC code. This is then sent with the message. On receipt, the receiver recalculates the HMAC code from the same secret key, and the message, and checks it against the received version. If they match, it validates both the sender and the message.

Now let’s look at how Google Tink generates MAC codes. For this we generate a new MAC key with getPrimitive() method for MacFactory, and then use the computeMac() to create and verifyMac() to check:

The following is the Golang code [here]:

package main

import (
"fmt"
"github.com/google/tink/go/mac"
"github.com/google/tink/go/keyset"
"github.com/google/tink/go/insecurecleartextkeyset"
"os"
"strconv"

)

func main() {

msg:="This is a test"


key,_ := keyset.NewHandle(mac.HMACSHA256Tag128KeyTemplate())
t:=1
argCount := len(os.Args[1:])
if (argCount>0) { msg= (os.Args[1])}


if argCount > 1 {
t,_ = strconv.Atoi(os.Args[2])
}

switch t {
case 1: key,_ =keyset.NewHandle(mac.HMACSHA256Tag256KeyTemplate())
case 2: key,_ =keyset.NewHandle(mac.HMACSHA256Tag128KeyTemplate())
case 3: key,_ =keyset.NewHandle(mac.HMACSHA512Tag256KeyTemplate())
case 4: key,_ =keyset.NewHandle(mac.HMACSHA512Tag512KeyTemplate())

}


m, _ := mac.New(key)

mac, _ := m.ComputeMAC([]byte(msg))


res:=m.VerifyMAC(mac, []byte(msg))

if (res!=nil) {
fmt.Printf("MAC failed!")
} else {

fmt.Printf("Message: %s\n\n", msg)

fmt.Printf("MAC: %x\n", mac)
fmt.Printf("\nMAC success!\n\n")

exportedPriv := &keyset.MemReaderWriter{}
insecurecleartextkeyset.Write(key, exportedPriv)
fmt.Printf("Key: %s\n\n", exportedPriv)
}

}

A sample run shows the creation of the MAC:

Message: Testing 123

MAC: 01ce880bae8fd62e6e9997f8f1c530348cc2f203baff099798b7247eb62f68e8eb4e070a76

MAC success!

Key: .{primary_key_id:3465022382 key:.key_data:.type_url:"type.googleapis.com/google.crypto.tink.HmacKey" value:"\022\004\010\003\020 \032 \031\257\017\320Cj\371b\036\211\224\337\266;4\341L\tF\3210\204\251\227\257\226\213\324a\317\350\255" key_material_type:SYMMETRIC . status:ENABLED key_id:3465022382 output_prefix_type:TINK . .nil.}

Raw MAC: CK6XoPQMEmgKXAoudHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuSG1hY0tleRIoEgQIAxAgGiAZrw/QQ2r5Yh6JlN+2OzThTAlG0TCEqZevlovUYc/orRgBEAEYrpeg9AwgAQ

Google Tink: Digital Signatures

One of the standard methods that we use in cryptography is to sign a message with a private key, and to prove the signing with the public. Thus if Bob has a key pair, he uses his private key to sign the message, and then Alice will prove that it was Bob who signed it, using Bob’s public key. It will also prove that the message has not been changed by Eve. In this case, , Bob has a private key (sk) which he uses to sign a hash of the message, and then Alice uses his public key (pk) to prove the signature:

We use this method is many applications. An example is in Bitcoin transfers and where Bob signs a transaction to pay Alice a given number of bitcoins. He signs this transaction with his private key (which is in his wallet), and then adds it, with his public key onto the blockchain. Anyone who wants to check the transfer will check the signature with Bob’s public key.

We can then use the private key of the key pair to sign a message with the sign() method [here]:

// https://asecuritysite.com/encryption/go_tink03
package main

import (
"fmt"
"os"
"github.com/google/tink/go/keyset"
"github.com/google/tink/go/signature"
"github.com/google/tink/go/insecurecleartextkeyset"
)

func main() {

msg:="This is a test"

argCount := len(os.Args[1:])
if (argCount>0) { msg= (os.Args[1])}



keyPriv, _ := keyset.NewHandle(signature.ECDSAP256KeyTemplate())
// keyPriv, _ := keyset.NewHandle(signature.ECDSAP384KeyTemplate())
// keyPriv, _ := keyset.NewHandle(signature.ECDSAP521KeyTemplate())


keyPub, _ := keyPriv.Public()

s, _ := signature.NewSigner(keyPriv)
a, _ := s.Sign([]byte(msg))
v, _ := signature.NewVerifier(keyPub)

res := v.Verify(a, []byte(msg))

if (res != nil) {
fmt.Println("Signature verification failed")
} else {
fmt.Printf("Message: %s\n\n", msg)

fmt.Printf("Signature: %x\n", a)

fmt.Printf("\nPrivate Key: %s\n\n", keyPriv)
fmt.Printf("Public Key: %s\n\n", keyPub)
fmt.Println("Signature verification succeeded.\n\n")

exportedPriv := &keyset.MemReaderWriter{}
insecurecleartextkeyset.Write(keyPriv, exportedPriv)
fmt.Printf("Key: %s\n\n", exportedPriv)

exportedPub := &keyset.MemReaderWriter{}
insecurecleartextkeyset.Write(keyPub, exportedPub)
fmt.Printf("Key: %s\n\n", exportedPub)
}

}

A sample run is:

Message: Testing 123
Signature: 018f8f01de3045022100e28c729ca7c8a18c8281365468a818624260a99d495ce38637529496bf86b2b9022005e7e00d3a7d90ab41144e180a82c5c8a874442853f3b176dd2b81160f7b9268
Private Key: primary_key_id:2408514014 key_info:.type_url:"type.googleapis.com/google.crypto.tink.EcdsaPrivateKey" status:ENABLED key_id:2408514014 output_prefix_type:TINK .
Public Key: primary_key_id:2408514014 key_info:.type_url:"type.googleapis.com/google.crypto.tink.EcdsaPublicKey" status:ENABLED key_id:2408514014 output_prefix_type:TINK .
Signature verification succeeded.
Private Key ID: 2408514014
Private Key [key_data:.type_url:"type.googleapis.com/google.crypto.tink.EcdsaPrivateKey" value:"\022L\022\006\010\003\020\002\030\002\032 lfsG`E\350\3730\242s\031\250I\347p9\r\0166+o\361\030\306\243\331\204\305\314\177\323\" \237\215\t_\214\005\033\3542\226\323.\023\033\0327\327\373\374\364\2627E\3224\356\307\301A\3216x\032 \307\304\032u\266^\232fy\337\311 \331;\326s\257\204.\211\314\371N\302\222\033\013Mr\371\334\247" key_material_type:ASYMMETRIC_PRIVATE . status:ENABLED key_id:2408514014 output_prefix_type:TINK ]:
Public Key ID: 2408514014
Public Key [key_data:.type_url:"type.googleapis.com/google.crypto.tink.EcdsaPublicKey" value:"\022\006\010\003\020\002\030\002\032 lfsG`E\350\3730\242s\031\250I\347p9\r\0166+o\361\030\306\243\331\204\305\314\177\323\" \237\215\t_\214\005\033\3542\226\323.\023\033\0327\327\373\374\364\2627E\3224\356\307\301A\3216x" key_material_type:ASYMMETRIC_PUBLIC . status:ENABLED key_id:2408514014 output_prefix_type:TINK ]:

But, ECDSA can struggle to scale in a distributed manner, and can be vulnerable to weakly generated nonce values (k). In this case will will find the Ed25519 signature for a message, and where we use a private key to sign, and a public key to prove the signature:

In the following we will use Curve 255219 to produce an Ed25519 signature [here]:

// https://asecuritysite.com/encryption/go_tink04
package main
import (
"fmt"
"os"
"github.com/google/tink/go/keyset"
"github.com/google/tink/go/signature"
"github.com/google/tink/go/insecurecleartextkeyset"
)
func main() {
msg:="This is a test"
argCount := len(os.Args[1:])
if (argCount>0) { msg= (os.Args[1])}
keyPriv, _ := keyset.NewHandle(signature.ED25519KeyTemplate())
keyPub, _ := keyPriv.Public()

s, _ := signature.NewSigner(keyPriv)
a, _ := s.Sign([]byte(msg))
v, _ := signature.NewVerifier(keyPub)

res := v.Verify(a, []byte(msg))
if (res != nil) {
fmt.Println("Signature verification failed")
} else {
fmt.Printf("Message: %s\n\n", msg)
fmt.Printf("Signature: %x\n", a)
fmt.Printf("\nPrivate Key: %s\n\n", keyPriv)
fmt.Printf("Public Key: %s\n\n", keyPub)
fmt.Println("Signature verification succeeded.\n\n")
exportedPriv := &keyset.MemReaderWriter{}

insecurecleartextkeyset.Write(keyPriv, exportedPriv)

exportedPub := &keyset.MemReaderWriter{}
insecurecleartextkeyset.Write(keyPub, exportedPub)

fmt.Printf("Private Key ID: %d\n\n", exportedPriv.Keyset.GetPrimaryKeyId())
fmt.Printf("Private Key %s:\n\n", exportedPriv.Keyset.GetKey())

fmt.Printf("Public Key ID: %d\n\n", exportedPub.Keyset.GetPrimaryKeyId())
k:=exportedPub.Keyset.GetKey()
fmt.Printf("Public Key %s:\n\n", k)
}
}

And a sample run:

Message: Testing 123

Signature: 0111a675eb60a495450d675120afebad23ee0328e6a6e43df66530db23ef41b7cca523c4bdee308db32a6ad1b9b115d9b8a87dd002a11df1632ef95fc1123cf9b2acab0e0f

Private Key: primary_key_id:296121835 key_info:.type_url:"type.googleapis.com/google.crypto.tink.Ed25519PrivateKey" status:ENABLED key_id:296121835 output_prefix_type:TINK .

Public Key: primary_key_id:296121835 key_info:.type_url:"type.googleapis.com/google.crypto.tink.Ed25519PublicKey" status:ENABLED key_id:296121835 output_prefix_type:TINK .

Signature verification succeeded.


Private Key ID: 296121835

Private Key [key_data:.type_url:"type.googleapis.com/google.crypto.tink.Ed25519PrivateKey" value:"\022 \345\017\335g\372M\311\211ya\226\251\220V\375\203gF\036\226\364\353I\323\030*\237\200\354\267\241\303\032\"\022 8;\307Z\305\177\240\014\306fD\372\356\20084\365z\332o\360\377n\035\241\t\374{2\312\2557" key_material_type:ASYMMETRIC_PRIVATE . status:ENABLED key_id:296121835 output_prefix_type:TINK ]:

Public Key ID: 296121835

Public Key [key_data:.type_url:"type.googleapis.com/google.crypto.tink.Ed25519PublicKey" value:"\022 8;\307Z\305\177\240\014\306fD\372\356\20084\365z\332o\360\377n\035\241\t\374{2\312\2557" key_material_type:ASYMMETRIC_PUBLIC . status:ENABLED key_id:296121835 output_prefix_type:TINK ]:

Conclusions

OpenSSL is the Swiss Army knife of cybersecurity, but it does have flaws, especially that it carries so much legacy code. Tink is a good alternative, and designed for our modern set of applications:

https://asecuritysite.com/tink