clj.orcery

Language, Expression and Design

Wednesday

16

November 2016

hara.security - making sense of the JCA

by Chris Zheng,

There's a lot of noise around security and even at the best of times, it's hard to make sense of what is happening with so much choice of implementation and strategies. The JCA (Java Cryptography Architecture) provides a really nice high-level interface to explore and extend the many different implementations of algorithms, ciphers and keys.

To understand what to do with security, we have to understand the most basic forms of security that people are doing today. Broadly, they form into a couple of categories:

  • Encryption (To send a recieve secret messages)
    • Symmetric
    • Asymmetric
  • Verification (To ensure that the message is in fact from the correct person)
    • Digests
    • Signatures

All these methods (except the hash-digests) involve keys.

keys as data

hara.security ensures that keys can be expressed as explicitly as possible. For example, key data is defined as such:

(def aes-data
  {:type "AES",
   :mode :secret,
   :format "RAW",
   :encoded "euHlt5sHWhRpbKZHjrwrrQ=="}

and can be turned into a AES key as follows:

(use 'hara.security)
(require '[hara.io.encode :as encode])
(def aes-key (map->key aes-data))

Exposing keys is not the greatest idea in general and code that work with keys should be committed with caution. However, having keys that can be outputted to data as well as then read back from data makes development and debugging much faster. There is greater explicity and less guesswork.

providers and services

A little bit of prior knowledge of the JCA is required in order to work with the library. At the top level are security providers that publish services that they offer. We can see all of the providers

(list-providers)
=> ["Apple" "SUN" "SunEC" "SunJCE" "SunJGSS"
    "SunJSSE" "SunPCSC" "SunRsaSign" "SunSASL" "XMLDSig"]

If additional providers are installed (most people will tend to use BouncyCastle or some sort of a Hardware based library) - it will show up with with the call to list-providers.

Each of the providers publish services that can be used within the JCA framework. The different types of services are listed as follows:

(list-services)
=> ("AlgorithmParameterGenerator" "AlgorithmParameters"
    "CertPathBuilder" "CertPathValidator" "CertStore"
    "CertificateFactory" "Cipher" "Configuration"
    "GssApiMechanism" "KeyAgreement" "KeyFactory"
    "KeyGenerator" "KeyInfoFactory" "KeyManagerFactory"
    "KeyPairGenerator" "KeyStore" "Mac" "MessageDigest"
    "Policy" "SSLContext" "SaslClientFactory" "SaslServerFactory"
    "SecretKeyFactory" "SecureRandom" "Signature" "TerminalFactory"
    "TransformService" "TrustManagerFactory" "X509Store"
    "X509StreamParser" "XMLSignatureFactory")

and accessed. For example here we list the types of Ciphers that are avaliable:

(list-services "Cipher")
=> ("AES" "AESRFC3211WRAP" "AESRFC5649WRAP" "AESWRAP"
    ...
    "SM4" "Serpent" "Shacal2" "TEA" "Threefish-1024"
    "Threefish-256" "Threefish-512" "Tnepres" "Twofish"
    "VMPC" "VMPC-KSA3" "XSALSA20" "XTEA")

Services can be also listed by type and provider:

(list-services "Cipher" "SunJCE")
=> ("AES" "AESWrap" "AESWrap_128" "AESWrap_192" ... "RC2" "RSA")

hara.security only uses a subset of services - mainly KeyGenerators, Cipher (for encryption) as well as Mac, MessageDigest and Signature for verification.

symmetric encryption

Lets look at the types of keys. The most simple is a shared key for symmetric encryption:

(generate-key "AES" {:length 128})
=> #key {:type "AES", 
         :mode :secret, 
         :format "RAW", 
         :encoded "RVV1axot5sUclfHiPt3w4A=="}

Symmetric encryption is relatively straightforward. We can encrypt a value with a given key:

(-> (encrypt (.getBytes "hello world")
             {:type "AES",
              :mode :secret,
              :format "RAW",
              :encoded "euHlt5sHWhRpbKZHjrwrrQ=="})
    (encode/to-hex))
=> "30491ab4427e45909f3d2f5d600b0f93"

Decryption is done using the same key:

(-> (decrypt (encode/from-hex  "30491ab4427e45909f3d2f5d600b0f93")
             {:type "AES",
              :mode :secret,
              :format "RAW",
              :encoded "euHlt5sHWhRpbKZHjrwrrQ=="})
    (String.))
=> "hello world"

asymmetric encryption

Then there is asymmetric encryption using a public/private pair of keys. The pair can be generated as follows:

(def pair (generate-key-pair "RSA" {:length 512}))

Asymmetric encryption is the same, except that it uses the public key for encryption

  (println pair)
  => [#key {:type "RSA",
            :mode :public,
            :format "X.509",
            :encoded "MFwwDQYJKoZIhvcNAQ ... CAwEAAQ=="}
      #key {:type "RSA",
            :mode :private,
            :format "PKCS#8",
            :encoded "MIIBVgIBADANBgkqhe ... UaT0sdA=="}]
  (-> (encrypt (.getBytes "hello world")
               (first pair))
      (encode/to-base64))
  => "L0JY9+awGKyFT7cERLcIeRopcWs6Zi576DUXPe7aCMYpO9vt6l3dlcv14EZiOS5r3uq2bPBtGvTC8lyWMtYCKQ=="

and the private key for decryption

  (-> (encode/from-base64 "L0JY9+awGKyFT7cERLcIeRopcWs6Zi576DUXPe7aCMYpO9vt6l3dlcv14EZiOS5r3uq2bPBtGvTC8lyWMtYCKQ==")
      (decrypt (second pair))
      (String.))
  => "hello world"

digests

Now for digests. This generates a unique hash so that files can be verified for integrity. digest does not require a key, whilst hmacs do. Here are examples of their usage:

(digest)
=> ["MD2" "MD5" "SHA" "SHA-224"
    "SHA-256" "SHA-384" "SHA-512"]

(-> (digest (.getBytes "hello world")
            "SHA")
    (encode/to-hex))
=> "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"

(-> (hmac (.getBytes "hello world")
          {:type "HmacSHA1",
           :mode :secret,
           :format "RAW",
           :encoded "wQ0lyydDSEFRKviwv/2BoWVQDpj8hbUiUXytuWj7Yv8="})
    (encode/to-hex))
=> "a6f9e08fad62f63a35c6fd320f4249c9ad3079dc"

signatures

The last is a signature. Signatures are generated using the private key and verified using a public key. The example can be seen there. Lets list the algorithms that are avaliable:

(sign)
=> ["MD2withRSA" "MD5andSHA1withRSA" "MD5withRSA"
    "NONEwithDSA" "NONEwithECDSA" "SHA1withDSA"
    "SHA1withECDSA" "SHA1withRSA" "SHA224withDSA"
    "SHA224withECDSA" "SHA224withRSA" "SHA256withDSA"
    "SHA256withECDSA" "SHA256withRSA" "SHA384withECDSA"
    "SHA384withRSA" "SHA512withECDSA" "SHA512withRSA"]

We can generate a signature:

(def signature
  (sign (.getBytes "hello world")
          (second pair)
        {:algorithm "MD5withRSA"}))

And the verify it:

(verify (.getBytes "hello world")
        signature
        (first pair)
        {:algorithm "MD5withRSA"})
=> true

So that's a quick run down of the library. I hope it makes security a little bit more pleasant to work with. Please read the docs.

comments powered by Disqus