# coding=utf-8
"""Simple public key authentication with RSA based on the
implementation
http://code.activestate.com/recipes/578797-public-key-encryption-rsa/"""
from __future__ import division, absolute_import
from base64 import b32encode,b32decode
try:
from fractions import gcd
except ImportError:
from math import gcd
from random import randrange
from collections import namedtuple
from math import log
from binascii import hexlify, unhexlify
import binascii
import sys
from PyFoam.ThirdParty.six import print_,PY3,binary_type,u
if PY3:
range_func = range
else:
range_func = xrange
from PyFoam.Infrastructure.Hardcoded import authDirectory,assertDirectory
from PyFoam.FoamInformation import getUserName
from PyFoam.Error import warning
from os import path
[docs]def myPrivateKeyFile():
return path.join(authDirectory(),"privateKey")
[docs]def myPublicKeyFile():
return path.join(authDirectory(),"publicKey")
[docs]def myAuthenticatedKeysFile():
return path.join(authDirectory(),"myAuthenticatedKeys")
[docs]def ensureKeyPair():
from os import chmod
assertDirectory(authDirectory(),dirMode="700")
if PY3:
perm=eval("0o700")
else:
perm=eval("0700")
if not path.exists(myPrivateKeyFile()) and not path.exists(myPublicKeyFile()):
warning("No key pair in",authDirectory()," .... Creating")
pubkey, privkey = keygen(2 ** 64)
open(myPublicKeyFile(),"w").write(key_to_str(pubkey))
open(myPrivateKeyFile(),"w").write(key_to_str(privkey))
chmod(myPublicKeyFile(),perm)
chmod(myPrivateKeyFile(),perm)
if not path.exists(myAuthenticatedKeysFile()):
f=open(myAuthenticatedKeysFile(),"w")
f.close()
chmod(myAuthenticatedKeysFile(),perm)
[docs]def myPublicKeyText():
return open(myPublicKeyFile()).read()
[docs]def myPublicKey():
return str_to_key(open(myPublicKeyFile()).read())
[docs]def myPrivateKey():
return str_to_key(open(myPrivateKeyFile()).read())
[docs]def is_prime(n, k=30):
# http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
if n <= 3:
return n == 2 or n == 3
neg_one = n - 1
# write n-1 as 2^s*d where d is odd
s, d = 0, neg_one
while not d & 1:
s, d = s + 1, d >> 1
assert 2 ** s * d == neg_one and d & 1
for _ in range_func(k):
a = randrange(2, neg_one)
x = pow(a, d, n)
if x in (1, neg_one):
continue
for _ in range_func(s - 1):
x = x ** 2 % n
if x == 1:
return False
if x == neg_one:
break
else:
return False
return True
[docs]def randprime(n=10 ** 8):
p = 1
while not is_prime(p):
p = randrange(n)
return p
[docs]def multinv(modulus, value):
"""
Multiplicative inverse in a given modulus
>>> multinv(191, 138)
18
>>> multinv(191, 38)
186
>>> multinv(120, 23)
47
"""
# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
x, lastx = 0, 1
a, b = modulus, value
while b:
a, q, b = b, a // b, a % b
x, lastx = lastx - q * x, x
result = (1 - lastx * modulus) // value
if result < 0:
result += modulus
assert 0 <= result < modulus and value * result % modulus == 1
return result
KeyPair = namedtuple('KeyPair', 'public private')
Key = namedtuple('Key', 'exponent modulus')
[docs]def keygen(n, public=None):
""" Generate public and private keys from primes up to N.
Optionally, specify the public key exponent (65537 is popular choice).
>>> pubkey, privkey = keygen(2**64)
>>> msg = 123456789012345
>>> coded = pow(msg, *pubkey)
>>> plain = pow(coded, *privkey)
>>> assert msg == plain
"""
# http://en.wikipedia.org/wiki/RSA
prime1 = randprime(n)
prime2 = randprime(n)
composite = prime1 * prime2
totient = (prime1 - 1) * (prime2 - 1)
if public is None:
private = None
while True:
private = randrange(totient)
if gcd(private, totient) == 1:
break
public = multinv(totient, private)
else:
private = multinv(totient, public)
assert public * private % totient == gcd(public, totient) == gcd(private, totient) == 1
assert pow(pow(1234567, public, composite), private, composite) == 1234567
return KeyPair(Key(public, composite), Key(private, composite))
[docs]def encode(msg, pubkey, verbose=False):
chunksize = int(log(pubkey.modulus, 256))
outchunk = chunksize + 1
outfmt = '%%0%dx' % (outchunk * 2,)
bmsg = msg if isinstance(msg, binary_type) else msg.encode('utf-8')
result = []
for start in range_func(0, len(bmsg), chunksize):
chunk = bmsg[start:start + chunksize]
chunk += b'\x00' * (chunksize - len(chunk))
plain = int(hexlify(chunk), 16)
coded = pow(plain, *pubkey)
bcoded = unhexlify((outfmt % coded).encode())
if verbose:
print_('Encode:', chunksize, chunk, plain, coded, bcoded)
result.append(bcoded)
return b''.join(result)
[docs]def decode(bcipher, privkey, verbose=False):
try:
chunksize = int(log(privkey.modulus, 256))
outchunk = chunksize + 1
outfmt = '%%0%dx' % (chunksize * 2,)
result = []
for start in range_func(0, len(bcipher), outchunk):
bcoded = bcipher[start: start + outchunk]
coded = int(hexlify(bcoded), 16)
plain = pow(coded, *privkey)
chunk = unhexlify((outfmt % plain).encode())
if verbose:
print_('Decode:', chunksize, chunk, plain, coded, bcoded)
result.append(chunk)
return b''.join(result).rstrip(b'\x00').decode('utf-8')
except (UnicodeDecodeError,binascii.Error,TypeError) as e:
return u(b'Failed decode for'+bcipher)
[docs]def key_to_str(key):
"""
Convert `Key` to string representation
>>> key_to_str(Key(50476910741469568741791652650587163073, 95419691922573224706255222482923256353))
'25f97fd801214cdc163796f8a43289c1:47c92a08bc374e96c7af66eb141d7a21'
"""
return ':'.join((('%%0%dx' % ((int(log(number, 256)) + 1) * 2)) % number) for number in key)
[docs]def str_to_key(key_str):
"""
Convert string representation to `Key` (assuming valid input)
>>> (str_to_key('25f97fd801214cdc163796f8a43289c1:47c92a08bc374e96c7af66eb141d7a21') ==
... Key(exponent=50476910741469568741791652650587163073, modulus=95419691922573224706255222482923256353))
True
"""
return Key(*(int(number, 16) for number in key_str.split(':')))
[docs]def createChallengeString(msg):
return (b32encode(msg.encode('utf-8'))+b":"+b32encode(encode(msg,myPrivateKey()))).decode()
[docs]def checkChallenge(challenge,pubKey):
org,encoded=challenge.split(":")
org=b32decode(org).decode('utf-8')
encoded=decode(b32decode(encoded),pubKey)
return org==encoded
[docs]def authenticatedKeys():
keys={}
for l in open(myAuthenticatedKeysFile()).readlines():
try:
u,k=l.split()
keys[u]=k
except ValueError:
pass
return keys
[docs]def checkAuthentication(userName,challenge):
if userName==getUserName():
pub=myPublicKey()
else:
pub=None
keys=authenticatedKeys()
if userName in keys:
pub=str_to_key(keys[userName])
else:
return False
return checkChallenge(challenge,pub)
if __name__ == '__main__':
ensureKeyPair()
pub=myPublicKey()
priv=myPrivateKey()
msg = u'the quick brown fox jumped over the lazy dog ® ⌀'
challenge=createChallengeString(msg)
print_("Challenge:",challenge)
print_("Checking:",checkChallenge(challenge,myPublicKey()))
pubkey, privkey = keygen(2 ** 64)
print_("Checking False:",False==checkChallenge(challenge,pubkey))
print_("Checking MyUsername:",checkAuthentication(getUserName(),challenge))
print_("Checking user 'test':",checkAuthentication("test",challenge))