├── README.md ├── UNLICENSE └── rsa.el /README.md: -------------------------------------------------------------------------------- 1 | # RSA Cryptography in Emacs Lisp 2 | 3 | This is an Emacs Lisp implementation of the [RSA public-key 4 | cryptosystem][rsa]. Emacs' calc is used for big integer operations. 5 | Keys are generated from `/dev/urandom`. 6 | 7 | This package doesn't deal with protocols or key storage (e.g. the hard 8 | parts). It's only math functions. 9 | 10 | Read more: [RSA Signatures in Emacs Lisp][blog] 11 | 12 | ## Quick Demo 13 | 14 | Here's an example using a (very short) 128-bit key. 15 | 16 | ~~~el 17 | (setf message "hello, world!") 18 | 19 | (setf keypair (rsa-generate-keypair 128)) 20 | ;; => (:public (:n "74924929503799951536367992905751084593" 21 | ;; :e "65537") 22 | ;; :private (:n "74924929503799951536367992905751084593" 23 | ;; :d "36491277062297490768595348639394259869")) 24 | 25 | (setf sig (rsa-sign (plist-get keypair :private) message)) 26 | ;; => "1FA3ENRWZS66U8CKL6TT3VU0U" 27 | 28 | (rsa-verify (plist-get keypair :public) message sig) 29 | ;; => t 30 | ~~~ 31 | 32 | Larger keys can take many minutes to generate and compute signatures. 33 | 34 | 35 | [rsa]: https://en.wikipedia.org/wiki/RSA_(cryptosystem) 36 | [blog]: http://nullprogram.com/blog/2015/10/30/ 37 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /rsa.el: -------------------------------------------------------------------------------- 1 | ;;; rsa.el --- RSA crypto in Emacs Lisp -*- lexical-binding: t; -*- 2 | 3 | ;; This is free and unencumbered software released into the public domain. 4 | 5 | ;; Author: Christopher Wellons 6 | ;; URL: https://github.com/skeeto/emacs-rsa 7 | 8 | ;;; Commentary: 9 | 10 | ;; RSA signature algorithms built on Emacs' calc. 11 | 12 | ;; Quick start: 13 | 14 | ;; (setf keypair (rsa-generate-keypair 1024)) 15 | ;; (setf message "hello, world!") 16 | ;; (setf sig (rsa-sign (plist-get keypair :private) message)) 17 | ;; (rsa-verify (plist-get keypair :public) message sig) 18 | 19 | ;; For large keys you may need to adjust `max-lisp-eval-depth' and 20 | ;; `max-specpdl-size', just as you would for other large calc 21 | ;; operations. 22 | 23 | ;; Time estimates (Emacs 24.4 + Core i7): 24 | 25 | ;; Keylen Key generation Signature generation 26 | ;; 512 bits 12 sec 3 sec 27 | ;; 1024 bits 2 min 11 sec 28 | ;; 2048 bits 29 min 1 min 29 | 30 | ;; Signature verification time is negligible. 31 | 32 | ;;; Code: 33 | 34 | (require 'calc) 35 | (require 'cl-lib) 36 | 37 | (defun rsa--buffer-to-calc-hex () 38 | "Return a calc number of the bytes of the current buffer." 39 | (let ((f (apply-partially #'format "%02x"))) 40 | (concat "16#" (mapconcat f (buffer-string) "")))) 41 | 42 | (defun rsa-generate-prime (bits) 43 | "Generate a random prime number of BITS length from /dev/urandom." 44 | (with-temp-buffer 45 | (set-buffer-multibyte nil) 46 | (call-process "head" "/dev/urandom" (current-buffer) nil 47 | "-c" (number-to-string (/ bits 8))) 48 | (calc-eval "nextprime($1, 10)" nil (rsa--buffer-to-calc-hex)))) 49 | 50 | (defun rsa--inverse (a n) 51 | "Multiplicative inverse using extended Euclidean algorithm." 52 | (let ((y 0) 53 | (r n) 54 | (newy 1) 55 | (newr a)) 56 | (while (calc-eval "$1 != 0" 'pred newr) 57 | (let ((quotient (calc-eval "$1 \\ $2" nil r newr))) 58 | (cl-psetf y newy 59 | newy (calc-eval "$1 - $2 * $3" nil 60 | y quotient newy)) 61 | (cl-psetf r newr 62 | newr (calc-eval "$1 - $2 * $3" nil 63 | r quotient newr)))) 64 | (when (calc-eval "$1 > 1" 'pred r) 65 | (error "not invertable")) 66 | (if (calc-eval "$1 < 0" 'pred y) 67 | (calc-eval "$1 + $2" nil y n) 68 | y))) 69 | 70 | (defun rsa-generate-keypair (bits) 71 | "Generate a fresh RSA keypair plist of BITS length." 72 | (let* ((p (rsa-generate-prime (+ 1 (/ bits 2)))) 73 | (q (rsa-generate-prime (+ 1 (/ bits 2)))) 74 | (n (calc-eval "$1 * $2" nil p q)) 75 | (i (calc-eval "($1 - 1) * ($2 - 1)" nil p q)) 76 | (e (calc-eval "2^16+1")) 77 | (d (rsa--inverse e i))) 78 | `(:public (:n ,n :e ,e) :private (:n ,n :d ,d)))) 79 | 80 | (defun rsa--mod-pow (base exponent modulus) 81 | "Modular exponentiation using right-to-left binary method." 82 | (let ((result 1)) 83 | (setf base (calc-eval "$1 % $2" nil base modulus)) 84 | (while (calc-eval "$1 > 0" 'pred exponent) 85 | (when (calc-eval "$1 % 2 == 1" 'pred exponent) 86 | (setf result (calc-eval "($1 * $2) % $3" nil result base modulus))) 87 | (setf exponent (calc-eval "$1 \\ 2" nil exponent) 88 | base (calc-eval "($1 * $1) % $2" nil base modulus))) 89 | result)) 90 | 91 | (defun rsa--encode-sig (number) 92 | "Encode signature as short string." 93 | (substring (calc-eval '("$1" calc-number-radix 36) nil number) 3)) 94 | 95 | (defun rsa--decode-sig (sig) 96 | (concat "36#" sig)) 97 | 98 | (cl-defun rsa-sign (private-key object &optional (hash-algo 'sha384)) 99 | "Compute the base-36 signature by PRIVATE-KEY for OBJECT. 100 | OBJECT is a buffer or string. HASH-ALGO must be a valid symbol 101 | for the first argument of `secure-hash'." 102 | (let ((n (plist-get private-key :n)) 103 | (d (plist-get private-key :d)) 104 | (hash (concat "16#" (secure-hash hash-algo object)))) 105 | (while (calc-eval "$1 > $2" 'pred hash n) 106 | (setf hash (calc-eval "$1 \\ 2" nil hash))) 107 | (rsa--encode-sig (rsa--mod-pow hash d n)))) 108 | 109 | (cl-defun rsa-verify (public-key object sig &optional (hash-algo 'sha384)) 110 | "Return non-nil nil if the signature matches PUBLIC-KEY for OBJECT. 111 | HASH-ALGO must match the algorithm used in generating the signature." 112 | (let ((n (plist-get public-key :n)) 113 | (e (plist-get public-key :e)) 114 | (hash (concat "16#" (secure-hash hash-algo object)))) 115 | (while (calc-eval "$1 > $2" 'pred hash n) 116 | (setf hash (calc-eval "$1 \\ 2" nil hash))) 117 | (let* ((result (rsa--mod-pow (rsa--decode-sig sig) e n))) 118 | (calc-eval "$1 == $2" 'pred result hash)))) 119 | 120 | (cl-defun rsa--stretch-passphrase (passphrase bits &optional (iter 500000)) 121 | "Stretch passphrase to a size of BITS over ITER hash iterations. 122 | Currently unused." 123 | (with-temp-buffer 124 | (set-buffer-multibyte nil) 125 | (dotimes (_ iter) 126 | (setf passphrase (secure-hash 'sha512 passphrase nil nil t))) 127 | (dotimes (i (ceiling bits 512)) 128 | (insert (secure-hash 'sha512 (format "%d%s" i passphrase) nil nil t))) 129 | (buffer-substring (point-min) (+ (point-min) (/ bits 8))))) 130 | 131 | (provide 'rsa) 132 | 133 | ;;; rsa.el ends here 134 | --------------------------------------------------------------------------------