Skip to content
/ tinklj Public

A Cryptographic Clojure Api for the Google Tink library

License

Notifications You must be signed in to change notification settings

perkss/tinklj

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tinklj

Tinklj a Clojure api for Google Tink cryptographic library, offering simplistic and secure cryptography for Clojure.

Installation

Clojars Project

Clojure CI

Leiningen/Boot

[tinklj "0.1.15-SNAPSHOT"]

Clojure CLI/deps.edn

tinklj {:mvn/version "0.1.15-SNAPSHOT"}

Gradle

compile 'tinklj:tinklj:0.1.15-SNAPSHOT'

Maven

<dependency>
  <groupId>tinklj</groupId>
  <artifactId>tinklj</artifactId>
  <version>0.1.15-SNAPSHOT</version>
</dependency>

Getting Started

The best way to find example use cases for the encryption techniques is to check the Acceptance Tests.

Initialize Tinklj

First we need to call register to register the encryption techniques. If you want to use all implementations of all primitives in tinklj then you call as follows:

(:require [tinklj.config :refer [register])

(register)

If you wish to use a specific implementation such as AEAD you use:

(:require [tinklj.config :refer [register])

(register :aead)

Available options are:

  • :aead
  • :daead
  • :mac
  • :signature
  • :streaming

To do custom initialization then registration proceeds directly with a Registry class.

(:require [tinklj.config :refer [register-key-manager])

(register-key-manager (MyAeadKeyManager.)

Generating New Key(set)s

Below shows how you can generate a keyset containing a randomly generated AES128-GCM specified.

(:require [tinklj.keys.keyset-handle :as keyset-handles])

(keyset-handles/generate-new :aes128-gcm)

Storing Keysets

Once you have generated the keyset you can store it down for future use. Cleartext and encrypted keysets can be stored. Obviously encrypted is recommended.

(:require [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.keysets.keyset-storage :as keyset-storage])

(def filename "my_keyset.json")
(def keyset-handle (keyset-handles/generate-new :aes128-gcm)

(keyset-storage/write-clear-text-keyset-handle keyset-handle filename)

Tinklj supports encrypting keysets with master keys stored in a remote key management systems. Lets see how to write these remote KMS keys.

(:require [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.keysets.keyset-storage :as keyset-storage]
          [tinklj.keysets.integration.gcp-kms-client :refer [gcp-kms-client])


;; Generate the key material...
(def keyset-handle (keyset-handles/generate-new :aes128-gcm)

;; and write it to a file...
(def filename "my_keyset.json")
;; encrypted with the this key in GCP KMS
(def master-key-uri = "gcp-kms://projects/tinklj-examples/locations/global/keyRings/foo/cryptoKeys/bar")
(def kms-client (gcp-kms-client))

(keyset-storage/write-remote-kms-keyset-handle
                    keyset-handle
                    kms-client
                    filename
                    master-key-uri)

Loading Existing Keysets

Once the keyset is stored it can be reloaded as follows:

(:require [tinklj.keysets.keyset-storage :as keyset-storage])

(keyset-storage/load-clear-text-keyset-handle filename)

Loading remote encrypted keys:

(:require [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.keysets.keyset-storage :as keyset-storage]
          [tinklj.keysets.integration.gcp-kms-client :refer [gcp-kms-client])


(def filename "my_keyset.json")
;; encrypted with the this key in GCP KMS
(def master-key-uri = "gcp-kms://projects/tinklj-examples/locations/global/keyRings/foo/cryptoKeys/bar")
(def kms-client (gcp-kms-client))

(keyset-storage/load-remote-kms-keyset-handle
                    kms-client
                    filename
                    master-key-uri)

Obtaining and Using Primitives

We then get the primitive of the keyset-handle and can use this to encrypt and decypt.

(keyset-handles/get-primitive keyset-handle)

Symmetric Key Encryption

Authenticated Encryption with Associated Data

(:require [tinklj.encryption.aead :refer [encrypt decrypt]
          [tinklj.keys.keyset-handle :as keyset-handle]
          [tinklj.primitives :as primitives])

;; 1. Generate the key material.
(def keyset-handle (keyset-handle/generate-new :aes128-gcm))

;; 2. Get the primitive.
(def aead (primitives/aead keyset-handle))

;; 3. Use the primitive to encrypt a plaintext,
(def ciphertext (encrypt aead (.getBytes data-to-encrypt) aad))

;; ... or to decrypt a ciphertext.
(def decrypted (decrypt aead ciphertext aad))

Deterministic Symmetric Key Encryption

Deterministic Authenticated Encryption with Associated Data

(:require [tinklj.encryption.daead :refer [encrypt decrypt]
          [tinklj.keys.keyset-handle :as keyset-handle]
          [tinklj.primitives :as primitives])


;; 1. Generate the key material.
(def keyset-handle (keyset-handle/generate-new :aes256-siv))

;; 2. Get the primitive.
(def daead (primitives/deterministic keyset-handle))

;; 3. Use the primitive to deterministically encrypt a plaintext,
(def ciphertext (encrypt daead (.getBytes data-to-encrypt) aad))

;; ... or to deterministically decrypt a ciphertext.
(def decrypted (decrypt daead ciphertext aad))

Symmetric Key Encryption of Streaming Data

;; 1. Generate the key material.
(def keyset-handle (keyset-handles/generate-new :aes128-ctr-hmac-sha256-4kb))

;; 2. Get the primitive.
(def streaming-primitive (primitives/streaming keyset-handle))

;; 3. Get the Encrypting Channel
(def encrypting-channel (streaming/encrypting-channel
                                      streaming-primitive
                                      ciphertext-destination
                                      associated-data))

;; 4. Write Encrypting Data
(streaming/encrypting-channel-write encrypting-channel byte-buffer)

;; 5. Get the Decrypting Channel
(def decrypting-channel (streaming/decrypting-channel
                                           streaming-primitive
                                           (.getChannel file-input-stream)
                                           associated-data))

;; 6. Read the Decrypted Data
(streaming/decrypting-channel-read decrypting-channel buf)

Message Authentication Code

How to compute or verify a MAC (Message Authentication Code)

(:require [tinklj.primitives :as primitives]
          [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.mac.message-authentication-code :as mac])

;; 1. Generate the key material.
(def keyset-handle (keyset-handles/generate-new :hmac-sha256-128bittag))

;; 2. Get the primitive.
(def mac-primitive (primitives/mac keyset-handle))

;; 3. Use the primitive to compute a tag,
(mac/compute mac-primitive data)

;; ... or to verify a tag.
(mac/verify mac-primitive tag data)

Digital Signatures

Here is an example of how to sign or verify a digital signature:

(:require [tinklj.primitives :as primitives]
          [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.signature.digital-signature :refer [sign verify])

;; SIGNING

;; 1. Generate the private key material.
(def private-keyset-handle (keyset-handles/generate-new :ecdsa-p256))

;; 2. Sign the data.
(def signature (sign private-keyset-handle data)

;; VERIFYING

;; 1. Obtain a handle for the public key material.
(def public-key-set-handle (get-public-keyset-handle private-keyset-handle))

;; 2. Use the primitive to verify.
(verify public-key-set-handle
        signature
        data)

Hybrid Encryption

To encrypt or decrypt using a combination of public key encryption and symmetric key encryption one can use the following:

(:require [tinklj.config :refer [register]]
  [tinklj.keys.keyset-handle :as keyset]
  [tinklj.primitives :as primitives]
  [tinklj.encryption.aead :refer [encrypt decrypt]])

(register)

;; 1. Generate the private key material.
(def private-keyset-handle (keyset/generate-new :ecies-p256-hkdf-hmac-sha256-aes128-gcm))

;;  Obtain the public key material.
(def public-keyset-handle (keyset/get-public-keyset-handle private-keyset-handle))

;; ENCRYPTING

;; 2. Get the primitive.
(def hybrid-encrypt (primitives/hybrid-encryption public-keyset-handle))

;; 3. Use the primitive.
(def encrypted-data (encrypt hybrid-encrypt
                             (.getBytes plain-text)
                             aad))
;; DECRYPTING

;;  2. Get the primitive.
(def hybrid-decrypt (primitives/hybrid-decryption private-keyset-handle))

;; 3. Use the primitive.
(def decrypted-data (decrypt hybrid-decrypt
                             encrypted-data
                             aad))

Envelope Encryption

(:require  [tinklj.keys.keyset-handle :as keyset]
           [tinklj.primitives :as primitives]
           [tinklj.encryption.aead :refer [encrypt]]
           [tinklj.keysets.integration.kms-client :as client]
           [tinklj.keysets.integration.gcp-kms-client :refer [gcp-kms-client])

;; 1. Generate the key material.
    (def kmsKeyUri
        "gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar")

    (def keysetHandle (keyset/generate-new
        (keyset/create-kms-envelope-aead-key-template kmsKeyUri :aes128-gcm)))

;; 2. Register the KMS client.
    (def gcp-client (gcp-kms-client))
    (client/with-credentials gcp-client "credentials.json")
    (client/kms-client-add gcp-client)

;; 3. Get the primitive.
    (def aead (primitives/aead keyset-handle))

;; 4. Use the primitive.
    (def ciphertext (encrypt aead (.getBytes data-to-encrypt) aad))

Key Rotation

To complete key rotation you need a keyset-handle that contains the keyset that should be rotated, and a specification of the new key via the KeyTemplate map for example :aes128-gcm.

(:require [tinklj.keys.keyset-handle :as keyset-handles]
          [tinklj.keysets.keyset-storage :as keyset-storage])

(def keyset-handle (keyset-handles/generate-new :aes128-gcm)) ;; existing keyset
(def keyset-template (:hmac-sha256-128bittag keyset-handles/key-templates)) ;; template for the new key

(keyset-storage/rotate-keyset-handle keyset-handle keyset-template)

FAQ

  1. Exception: java.security.InvalidKeyException: Illegal key size or default parameters when getting a primitive

  • If you are running a version of java 8 below 1.8.0_162 you will need to download the (Java Cryptography Extension)[http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html]
  • If you are using a version of the JVM before 1.8.0_161 and 1.8.0_151 you will need to set (Security/setProperty "crypto.policy" "unlimited")
  • Above Java 8 Update 161 the default encryption strength is unlimited by default

See (this stack overflow question for more)[https://stackoverflow.com/questions/24907530/java-security-invalidkeyexception-illegal-key-size-or-default-parameters-in-and]

Feature List

Based on the available feature list defined here

  • Generation of keysets
  • Symmetric key encryption
  • Storing keysets
  • Loading existing keysets
  • Storing and loading encrypted keysets
  • Deterministic symmetric key encryption
  • Streaming symmetric key encryption
  • MAC codes
  • Digital signatures
  • Hybrid encryption
  • Envelope encryption
  • Key rotation

Contributions

Please feel free to pick up and issue or create issues and contribute.

Contributors

Thanks to all the developers involved!

Matt @lamp

Chris @minimal

Stuart @perkss

Logo

Thanks and credit for the great Tinklj logo!

J.G Designer @newfinal100

Disclaimer

This is not an official Google product or library.