In this step we will build the decrypt-cookie function. Given a cookie like:

{ :path "/" , :firstpartyonly 0 , :name "tz" , :persistent 0 , :encrypted_value "v10garbage" , :expires_utc 0 , :host_key "github.com" , :creation_utc 13147635893809723 , :httponly 0 , :priority 1 , :last_access_utc 13147635892809723 , :secure 1 , :has_expires 0 }

The decrypt-cookie function returns a decrypted cookie:

{ :path "/" , :firstpartyonly 0 , :name "tz" , :persistent 0 , :value "decrypted value" , :expires_utc 0 , :host_key "github.com" , :creation_utc 13147635893809723 , :httponly 0 , :priority 1 , :last_access_utc 13147635892809723 , :secure 1 , :has_expires 0 }

How do we know if a cookie is encrypted? As we said above, a cookie is encrypted if its encrypted_value field starts with v10. Let's use the -> macro to express this in the is-encrypted? function:

( defn is-encrypted? "Returns true if the provided encrypted value (bytes[]) is an encrypted value by Chrome that is, if it starts with v10" [ cookie ] (-> cookie ( :encrypted_value ) ( String. "UTF-8" ) ( clojure.string/starts-with? "v10" )))

But how do we decrypt an encrypted value? Since we use CBC, we need to combine the AES key and an initialization vector (again taken from the reference implementation) and build a cipher. Using this cipher we decrypt the encrypted text in the decrypt function:

( defn decrypt [ aeskey text ] "Decrypt AES encrypted text given an aes key" ( let [ ivsbytes (-> (repeat 16 " " ) ( clojure.string/join ) ( .getBytes )) iv ( IvParameterSpec. ivsbytes ) cipher ( Cipher/getInstance "AES/CBC/PKCS5Padding" ) _ ( .init cipher Cipher/DECRYPT_MODE aeskey iv )] ( String. ( .doFinal cipher text ))))

This is a little terse but can easily be decomposed into three steps:

Building the initial vector for the AES decryption (let block omitted):

ivsbytes (-> (repeat 16 " " ) ( clojure.string/join ) ( .getBytes )) iv ( IvParameterSpec. ivsbytes )

Building the cipher

cipher ( Cipher/getInstance "AES/CBC/PKCS5Padding" ) _ ( .init cipher Cipher/DECRYPT_MODE aeskey iv )]

This is a little ugly, because .init is used only for its side effect and we ignore its result …

Decrypting the text:

( String. ( .doFinal cipher text ))

Now that we know how to decrypt a value and detect encrypted values, we can put it all together into decrypt-cookie , a function that decrypts a cookie:

( defn decrypt-cookie "Given a cookie, return a cookie with decrypted value" [ aes-key cookie ] (-> cookie (assoc :value ( if ( is-encrypted? cookie ) ;; Drop 3 removes the leading "v10" ( ->> cookie ( :encrypted_value ) (drop 3 ) ( byte-array ) ( decrypt aes-key )) (-> cookie ( :value )))) ;; Remove the unused :encrypted_value entry (dissoc :encrypted_value )))