You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

309 lines
10 KiB
Scheme

;;; rsa.ss
;;; Bruce T. Smith, University of North Carolina at Chapel Hill
;;; (circa 1984)
;;; Updated for Chez Scheme Version 7, May 2005
;;; This is a toy example of an RSA public-key encryption system. It
;;; is possible to create users who register their public keys with a
;;; center and hide their private keys. Then, it is possible to have
;;; the users exchange messages. To a limited extent one can look at
;;; the intermediate steps of the process by using encrypt and decrypt.
;;; The encrypted messages are represented by lists of numbers.
;;; Example session:
#|
> (make-user bonzo)
Registered with Center
User: bonzo
Base: 152024296883113044375867034718782727467
Encryption exponent: 7
> (make-user bobo)
Registered with Center
User: bobo
Base: 244692569127295893294157219042233636899
Encryption exponent: 5
> (make-user tiger)
Registered with Center
User: tiger
Base: 138555414233087084786368622588289286073
Encryption exponent: 7
> (show-center)
User: tiger
Base: 138555414233087084786368622588289286073
Encryption exponent: 7
User: bobo
Base: 244692569127295893294157219042233636899
Encryption exponent: 5
User: bonzo
Base: 152024296883113044375867034718782727467
Encryption exponent: 7
> (send "hi there" bonzo bobo)
"hi there"
> (send "hi there to you" bobo bonzo)
"hi there to you"
> (decrypt (encrypt "hi there" bonzo bobo) tiger)
" #z R4WN Zbb E8J"
|#
;;; Implementation:
(module ((make-user user) show-center encrypt decrypt send)
;;; (make-user name) creates a user with the chosen name. When it
;;; creates the user, it tells him what his name is. He will use
;;; this when registering with the center.
(define-syntax make-user
(syntax-rules ()
[(_ uid)
(begin (define uid (user 'uid)) (uid 'register))]))
;;; (encrypt mesg u1 u2) causes user 1 to encrypt mesg using the public
;;; keys for user 2.
(define-syntax encrypt
(syntax-rules ()
[(_ mesg u1 u2) ((u1 'send) mesg 'u2)]))
;;; (decrypt number-list u) causes the user to decrypt the list of
;;; numbers using his private key.
(define-syntax decrypt
(syntax-rules ()
[(_ numbers u) ((u 'receive) numbers)]))
;;; (send mesg u1 u2) this combines the functions 'encrypt' and 'decrypt',
;;; calling on user 1 to encrypt the message for user 2 and calling on
;;; user 2 to decrypt the message.
(define-syntax send
(syntax-rules ()
[(_ mesg u1 u2) (decrypt (encrypt mesg u1 u2) u2)]))
;;; A user is capable of the following:
;;; - choosing public and private keys and registering with the center
;;; - revealing his public and private keys
;;; - retrieving user's private keys from the center and encrypting a
;;; message for that user
;;; - decrypting a message with his private key
(define user
(lambda (name)
(let* ([low (expt 2 63)] ; low, high = bounds on p and q
[high (* 2 low)]
[p 0] ; p,q = two large, probable primes
[q 0]
[n 0] ; n = p * q, base for modulo arithmetic
[phi 0] ; phi = lcm(p-1,q-1), not quite the Euler phi function,
; but it will serve for our purposes
[e 0] ; e = exponent for encryption
[d 0]) ; d = exponent for decryption
(lambda (request)
(case request
;; choose keys and register with the center
[register
(set! p (find-prime low high))
(set! q
(let loop ([q1 (find-prime low high)])
(if (= 1 (gcd p q1))
q1
(loop (find-prime low high)))))
(set! n (* p q))
(set! phi
(/ (* (1- p) (1- q))
(gcd (1- p) (1- q))))
(set! e
(do ([i 3 (+ 2 i)])
((= 1 (gcd i phi)) i)))
(set! d (mod-inverse e phi))
(register-center (cons name (list n e)))
(printf "Registered with Center~%")
(printf "User: ~s~%" name)
(printf "Base: ~d~%" n)
(printf "Encryption exponent: ~d~%" e)]
;; divulge your keys-- you should resist doing this...
[show-all
(printf "p = ~d ; q = ~d~%" p q)
(printf "n = ~d~%" n)
(printf "phi = ~d~%" (* (1- p) (1- q)))
(printf "e = ~d ; d = ~d~%" e d)]
;; get u's public key from the center and encode
;; a message for him
[send
(lambda (mesg u)
(let* ([public (request-center u)]
[base (car public)]
[exponent (cadr public)]
[mesg-list (string->numbers mesg base)])
(map (lambda (x) (expt-mod x exponent base))
mesg-list)))]
;; decrypt a message with your private key
[receive
(lambda (crypt-mesg)
(let ([mesg-list (map (lambda (x) (expt-mod x d n)) crypt-mesg)])
(numbers->string mesg-list)))])))))
;;; The center maintains the list of public keys. It can register
;;; new users, provide the public keys for any particular user, or
;;; display the whole public file.
(module (register-center request-center show-center)
(define public-keys '())
(define register-center
(lambda (entry)
(set! public-keys
(cons entry
(remq (assq (car entry) public-keys) public-keys)))))
(define request-center
(lambda (u)
(let ([a (assoc u public-keys)])
(when (null? a)
(error 'request-center
"User ~s not registered in center"
u))
(cdr a))))
(define show-center
(lambda ()
(for-each
(lambda (entry)
(printf "~%User: ~s~%" (car entry))
(printf "Base: ~s~%" (cadr entry))
(printf "Encryption exponent: ~s~%" (caddr entry)))
public-keys)))
)
;;; string->numbers encodes a string as a list of numbers
;;; numbers->string decodes a string from a list of numbers
;;; string->numbers and numbers->string are defined with respect to
;;; an alphabet. Any characters in the alphabet are translated into
;;; integers---their regular ascii codes. Any characters outside
;;; the alphabet cause an error during encoding. An invalid code
;;; during decoding is translated to a space.
(module (string->numbers numbers->string)
(define first-code 32)
(define last-code 126)
(define alphabet
; printed form of the characters, indexed by their ascii codes
(let ([alpha (make-string 128 #\space)])
(do ([i first-code (1+ i)])
((= i last-code) alpha)
(string-set! alpha i (integer->char i)))))
(define string->integer
(lambda (str)
(let ([ln (string-length str)])
(let loop ([i 0] [m 0])
(if (= i ln)
m
(let* ([c (string-ref str i)] [code (char->integer c)])
(when (or (< code first-code) (>= code last-code))
(error 'rsa "Illegal character ~s" c))
(loop (1+ i) (+ code (* m 128)))))))))
(define integer->string
(lambda (n)
(list->string
(map (lambda (n) (string-ref alphabet n))
(let loop ([m n] [lst '()])
(if (zero? m)
lst
(loop (quotient m 128)
(cons (remainder m 128) lst))))))))
; turn a string into a list of numbers, each no larger than base
(define string->numbers
(lambda (str base)
(letrec ([block-size
(do ([i -1 (1+ i)] [m 1 (* m 128)]) ((>= m base) i))]
[substring-list
(lambda (str)
(let ([ln (string-length str)])
(if (>= block-size ln)
(list str)
(cons (substring str 0 block-size)
(substring-list
(substring str block-size ln))))))])
(map string->integer (substring-list str)))))
; turn a list of numbers into a string
(define numbers->string
(lambda (lst)
(letrec ([reduce
(lambda (f l)
(if (null? (cdr l))
(car l)
(f (car l) (reduce f (cdr l)))))])
(reduce
string-append
(map (lambda (x) (integer->string x)) lst)))))
)
;;; find-prime finds a probable prime between two given arguments.
;;; find-prime uses a cheap but fairly dependable test for primality
;;; for large numbers, by first weeding out multiples of first 200
;;; primes, then applies Fermat's theorem with base 2.
(module (find-prime)
(define product-of-primes
; compute product of first n primes, n > 0
(lambda (n)
(let loop ([n (1- n)] [p 2] [i 3])
(cond
[(zero? n) p]
[(= 1 (gcd i p)) (loop (1- n) (* p i) (+ i 2))]
[else (loop n p (+ i 2))]))))
(define prod-first-200-primes (product-of-primes 200))
(define probable-prime
; first check is quick, and weeds out most non-primes
; second check is slower, but weeds out almost all non-primes
(lambda (p)
(and (= 1 (gcd p prod-first-200-primes))
(= 1 (expt-mod 2 (1- p) p)))))
(define find-prime
; find probable prime in range low to high (inclusive)
(lambda (low high)
(let ([guess
(lambda (low high)
(let ([g (+ low (random (1+ (- high low))))])
(if (odd? g) g (1+ g))))])
(let loop ([g (guess low high)])
(cond
; start over if already too high
[(> g high) (loop (guess low high))]
; if guess is probably prime, return
[(probable-prime g) g]
; don't bother with even guesses
[else (loop (+ 2 g))])))))
)
;;; mod-inverse finds the multiplicative inverse of x mod b, if it exists
(module (mod-inverse)
(define gcdx
; extended Euclid's gcd algorithm, x <= y
(lambda (x y)
(let loop ([x x] [y y] [u1 1] [u2 0] [v1 0] [v2 1])
(if (zero? y)
(list x u1 v1)
(let ([q (quotient x y)] [r (remainder x y)])
(loop y r u2 (- u1 (* q u2)) v2 (- v1 (* q v2))))))))
(define mod-inverse
(lambda (x b)
(let* ([x1 (modulo x b)] [g (gcdx x1 b)])
(unless (= (car g) 1)
(error 'mod-inverse "~d and ~d not relatively prime" x b))
(modulo (cadr g) b))))
)
)