This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns example.aes | |
(:require [clojure.string :as s]) | |
(:import java.security.SecureRandom | |
(javax.crypto Cipher SecretKeyFactory) | |
(javax.crypto.spec SecretKeySpec IvParameterSpec PBEKeySpec) | |
(javax.xml.bind DatatypeConverter) | |
(org.bouncycastle.jce.provider BouncyCastleProvider) | |
(java.security Security))) | |
(Security/addProvider (BouncyCastleProvider.)) | |
;; adopted from https://gist.github.com/cowboyrushforth/5963752 | |
(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" | |
[pair] | |
(let [[hex1 hex2] pair | |
hex-str (str hex1 hex2)] | |
(-> (DatatypeConverter/parseHexBinary hex-str) | |
(first)))) | |
(defn hex-string-to-bytes | |
"Turn a hex string into a byte array" | |
[string] | |
(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) | |
parse-fn))) | |
(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." | |
[text] | |
(->> (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" | |
[length] | |
(let [bs (byte-array length) | |
sr (SecureRandom.)] | |
(.nextBytes ^SecureRandom sr ^bytes bs) | |
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