Skip to content

Instantly share code, notes, and snippets.

Created November 8, 2019 18:11
What would you like to do?
(ns example.aes
(:require [clojure.string :as s])
(javax.crypto Cipher SecretKeyFactory)
(javax.crypto.spec SecretKeySpec IvParameterSpec PBEKeySpec)
(javax.xml.bind DatatypeConverter)
(org.bouncycastle.jce.provider BouncyCastleProvider)
( Security)))
(Security/addProvider (BouncyCastleProvider.))
;; adopted from
(def delimiter #"\$\$\$")
;; Do not change these! Ruby side of things needs to match:
(def key-type "PBKDF2WithHmacSHA1")
;; this is really AES-256-CBC when combined with above key form
(def cipher-type "AES/CBC/PKCS5Padding")
(defn hex-pair-to-byte
"Converts a pair of strings/chars representing a hex number
into a byte"
(let [[hex1 hex2] pair
hex-str (str hex1 hex2)]
(-> (DatatypeConverter/parseHexBinary hex-str)
(defn hex-string-to-bytes
"Turn a hex string into a byte array"
(let [byte-seq (->> (vec string) ; split the string
(partition 2) ; grab each pair, we know that they're 8bit
(map hex-pair-to-byte))] ; convert each pair into byte
;; build a byte array
(byte-array (count byte-seq) byte-seq)))
(defn bytes-to-hex-string [bytes]
(s/upper-case (apply str (map #(format "%02x" %) bytes))))
(defn do-crypto
"Given byte arrays of iv, salt and encrypted payload
perform decryption with given plain-text password."
[^bytes iv ^bytes salt ^bytes payload ^String password method]
;; first we have to extract the key based on the password, iv and salt
(let [factory (SecretKeyFactory/getInstance key-type)
;; ruby side of things uses a standard key of known length
spec (PBEKeySpec. (.toCharArray ^String password) salt 1024 256)
secret (.generateSecret factory spec)
;; yay, we got the key!
key (SecretKeySpec. (.getEncoded secret) "AES")
iv-spec (IvParameterSpec. iv)
cipher (Cipher/getInstance cipher-type)
mode (case method
:decrypt Cipher/DECRYPT_MODE
:encrypt Cipher/ENCRYPT_MODE)
parse-fn (case method
:decrypt #(String. ^bytes % "UTF-8")
:encrypt bytes-to-hex-string)]
;; kick off the cipher
(.init cipher mode key iv-spec)
;; cast decrypted bytes into a string
(.doFinal cipher payload)
(defn split-encrypted-parts-to-bytes
"Encrypted text comes in the form of
iv, salt, payload
joined with a `delimiter`.
Each part is a byte array encoded as a hex string.
Here we're splitting whole string and extracting each
part as byte array."
(->> (s/split text delimiter)
(map hex-string-to-bytes)))
(defn decrypt-with-password
"Given encrypted text in the form of {iv}$$${salt}$$${payload}
and the plain text password - perform encryption"
[text password]
(let [[iv salt payload] (split-encrypted-parts-to-bytes text)]
(do-crypto iv salt payload password :decrypt)))
(defn ^bytes rand-bytes
"Generates random byte array of given length"
(let [bs (byte-array length)
sr (SecureRandom.)]
(.nextBytes ^SecureRandom sr ^bytes bs)
(defn encrypt-with-password
"Given string encrypt data with given password and return in form {iv}$$${salt}$$${payload}
and the plain text password - perform encryption"
([payload password]
(let [iv (rand-bytes 16)
salt (rand-bytes 48)]
(encrypt-with-password payload iv salt password)))
([payload iv salt password]
(let [encrypted (do-crypto iv salt (.getBytes ^String payload) password :encrypt)]
(format "%s$$$%s$$$%s" (bytes-to-hex-string iv) (bytes-to-hex-string salt) encrypted))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment