├── 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 |
--------------------------------------------------------------------------------