Created
          March 1, 2016 23:34 
        
      - 
      
- 
        Save worace/ede27e1928d81e7ecc56 to your computer and use it in GitHub Desktop. 
    ALL MANNER OF CLJ RSA KEY SERIALIZATION
  
        
  
    
      This file contains hidden or 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 block-chain.pem | |
| (:require [clojure.java.io :as io] | |
| [clojure.string :refer [join split]] | |
| [block-chain.encoding :refer :all])) | |
| (defn keydata [reader] | |
| (->> reader | |
| (org.bouncycastle.openssl.PEMParser.) | |
| (.readObject))) | |
| (defn pem-string->key-pair [string] | |
| "Convert a PEM-formatted private key string to a public/private keypair. | |
| Returns java.security.KeyPair." | |
| (let [kd (keydata (io/reader (.getBytes string)))] | |
| (.getKeyPair (org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.) kd))) | |
| (defn der-string->pub-key [string] | |
| (let [non-wrapped (clojure.string/replace string #"\n" "") | |
| key-bytes (decode64 non-wrapped) | |
| spec (java.security.spec.X509EncodedKeySpec. key-bytes) | |
| key-factory (java.security.KeyFactory/getInstance "RSA")] | |
| (.generatePublic key-factory spec))) | |
| (defn der-file->public-key [filepath] | |
| (der-string->pub-key (slurp filepath))) | |
| (defn public-key->der-string [key] | |
| "Generate DER-formatted string for a public key." | |
| (clojure.string/replace (encode64 (.getEncoded key)) | |
| #"\n" | |
| "")) | |
| (defn der-string->private-key [string] | |
| (.generatePrivate (java.security.KeyFactory/getInstance "RSA") | |
| (java.security.spec.PKCS8EncodedKeySpec. | |
| (decode64 (.getBytes string))))) | |
| (defn private-key->public-key [private] | |
| (let [modulus (.getModulus private) | |
| exponent (.getPublicExponent private) | |
| public-spec (java.security.spec.RSAPublicKeySpec. modulus exponent) | |
| kf (java.security.KeyFactory/getInstance "RSA")] | |
| (.generatePublic kf public-spec))) | |
| (defn der-string->key-pair [string] | |
| (let [private (der-string->private-key string) | |
| public (private-key->public-key private)] | |
| {:private private :public public})) | |
| (defn der-file->key-pair [filepath] | |
| (der-string->key-pair (slurp filepath))) | |
| (defn private-key->der-string [private-key] | |
| (encode64 (.getEncoded private-key))) | |
| (defn pem-string->pub-key [string] | |
| "Convert a PEM-formatted public key string to an RSA public key. | |
| Returns sun.security.rsa.RSAPublicKeyImpl" | |
| (let [kd (keydata (io/reader (.getBytes string))) | |
| kf (java.security.KeyFactory/getInstance "RSA") | |
| spec (java.security.spec.X509EncodedKeySpec. (.getEncoded kd))] | |
| (.generatePublic kf spec))) | |
| (defn pem-file->public-key [filepath] | |
| "Read a file containing a PEM-formatted public key | |
| and return a matching key" | |
| (pem-string->pub-key (slurp filepath))) | |
| (defn pem-file->key-pair [filepath] | |
| "Read a file containing a PEM-formatted private key and | |
| return a matching keypair" | |
| (pem-string->key-pair (slurp filepath))) | |
| (defn format-pem-string [encoded key-type] | |
| "Takes a Base64-encoded string of key data and formats it | |
| for file-output following openssl's convention of wrapping lines | |
| at 64 characters and appending the appropriate header and footer for | |
| the specified key type" | |
| (let [chunked (->> encoded | |
| (partition 64 64 []) | |
| (map #(apply str %))) | |
| formatted (join "\n" chunked)] | |
| (str "-----BEGIN " key-type "-----\n" | |
| formatted | |
| "\n-----END " key-type "-----\n"))) | |
| (defn private-key->pem-string [key] | |
| "Convert RSA private keypair to a formatted PEM string for saving in | |
| a .pem file. By default these private keys will encode themselves as PKCS#8 | |
| data (e.g. when calling (.getEncoded private-key)), so we have to convert it | |
| to ASN1, which PEM uses (this seems to also be referred to as PKCS#1). | |
| More info here http://stackoverflow.com/questions/7611383/generating-rsa-keys-in-pkcs1-format-in-java" | |
| (-> (.getEncoded key) | |
| (org.bouncycastle.asn1.pkcs.PrivateKeyInfo/getInstance) | |
| (.parsePrivateKey) | |
| (.toASN1Primitive) | |
| (.getEncoded) | |
| (encode64) | |
| (format-pem-string "RSA PRIVATE KEY"))) | |
| (defn public-key->pem-string [key] | |
| "Generate PEM-formatted string for a public key. This is simply a base64 | |
| encoding of the key wrapped with the appropriate header and footer." | |
| (format-pem-string (encode64 (.getEncoded key)) | |
| "PUBLIC KEY")) | |
  
    
      This file contains hidden or 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 block-chain.wallet | |
| (:require [clojure.java.io :as io] | |
| [clojure.string :refer [join split]] | |
| [block-chain.pem :as pem] | |
| [block-chain.transactions :as transactions] | |
| [block-chain.encoding :refer :all])) | |
| ;; Thanks to http://nakkaya.com/2012/10/28/public-key-cryptography/ | |
| ;; for many of these snippets | |
| (java.security.Security/addProvider (org.bouncycastle.jce.provider.BouncyCastleProvider.)) | |
| (defn kp-generator [length] | |
| (doto (java.security.KeyPairGenerator/getInstance "RSA" "BC") | |
| (.initialize length))) | |
| (defn key-map [kp] | |
| {:private (.getPrivate kp) | |
| :public (.getPublic kp) | |
| :address (pem/public-key->der-string (.getPublic kp))}) | |
| (defn generate-keypair | |
| "Generate an RSA Keypair. Accepts optional length. Default key length is 2048." | |
| ([] (generate-keypair 2048)) | |
| ([length] (key-map (.generateKeyPair (kp-generator length))))) | |
| (defn encrypt [message public-key] | |
| "Perform RSA public key encryption of the given message (as a string). | |
| Returns a Base64 encoded string of the encrypted data." | |
| (encode64 | |
| (let [cipher (doto (javax.crypto.Cipher/getInstance "RSA/ECB/PKCS1Padding" "BC") | |
| (.init javax.crypto.Cipher/ENCRYPT_MODE public-key))] | |
| (.doFinal cipher (.getBytes message))))) | |
| (defn decrypt [message private-key] | |
| "RSA private key decryption of an encrypted message. Expects a base64-encoded string | |
| of the encrypted data." | |
| (apply str | |
| (map char (let [cipher (doto (javax.crypto.Cipher/getInstance "RSA/ECB/PKCS1Padding" "BC") | |
| (.init javax.crypto.Cipher/DECRYPT_MODE private-key))] | |
| (.doFinal cipher (decode64 message)))))) | |
| (defn verify [encoded-sig message public-key] | |
| "RSA public key verification of a signature. Takes signature as base64-encoded string | |
| of signature data and message as a string. Returns true/false if signature | |
| is valid." | |
| (let [msg-data (.getBytes message) | |
| signature (decode64 encoded-sig) | |
| sig (doto (java.security.Signature/getInstance "SHA256withRSA" "BC") | |
| (.initVerify public-key) | |
| (.update msg-data))] | |
| (.verify sig signature))) | |
| (def wallet-path (str (System/getProperty "user.home") "/.wallet.pem")) | |
| (defn wallet-exists? [] (.exists (io/as-file wallet-path))) | |
| (defn load-or-generate-keys! | |
| "Looks for wallet keypair to exist at ~/.wallet.pem. If found, loads | |
| that keypair to use as our wallet. If not, generates a new keypair and | |
| saves it in that location for future use." | |
| [] | |
| (if (wallet-exists?) | |
| (key-map (pem/pem-file->key-pair wallet-path)) | |
| (let [kp (generate-keypair)] | |
| (spit wallet-path | |
| (pem/private-key->pem-string (:private kp))) | |
| kp))) | |
| ;; get keypair as map with: | |
| ;; {:private RSAPrivateKey :public RSAPublicKey :address "pub-key-der-string"} | |
| (def keypair (load-or-generate-keys!)) | |
| (defn sign | |
| "RSA private key signing of a message. Takes message as string" | |
| [message private-key] | |
| (encode64 | |
| (let [msg-data (.getBytes message) | |
| sig (doto (java.security.Signature/getInstance "SHA256withRSA" "BC") | |
| (.initSign private-key (java.security.SecureRandom.)) | |
| (.update msg-data))] | |
| (.sign sig)))) | |
| (defn sign-txn | |
| "Takes a transaction map consisting of :inputs and :outputs, where each input contains | |
| a Source TXN Hash and Source Output Index. Signs each input by adding :signature | |
| which contains an RSA-SHA256 signature of the JSON representation of all the outputs in the transaction." | |
| [txn key] | |
| (let [signable (transactions/txn-signable txn)] | |
| (assoc txn | |
| :inputs | |
| (into [] (map (fn [i] (assoc i :signature (sign signable key))) | |
| (:inputs txn)))))) | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment