Skip to content

Commit

Permalink
Merge pull request #94 from Jc2k/gmpy2-not-required
Browse files Browse the repository at this point in the history
Avoid gmpy2 dependency
  • Loading branch information
jlusiardi committed Jan 20, 2019
2 parents f5a4470 + a074b7d commit ab52150
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ python:
- "3.6"
before_install:
- sudo apt-get update
- sudo apt-get install -y libgmp-dev libmpfr-dev libmpc-dev libffi-dev build-essential python3-pip python3-dev
- sudo apt-get install -y libffi-dev build-essential python3-pip python3-dev
- pip install coveralls
script: coverage run -m unittest -v
after_success:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ The code presented in this repository was created based on release R1 from 2017-

# Installation

Since the code relies on **gmpy2** for large numbers some development libraries and a compiler is required:
The code relies on some C libraries so some development packages and a compiler are required:

So for debian:
```bash
apt install libgmp-dev libmpfr-dev libmpc-dev libffi-dev build-essential python3-pip python3-dev
apt install libffi-dev build-essential python3-pip python3-dev
```

After that use **pip3** to install the package:
Expand Down
5 changes: 2 additions & 3 deletions homekit/accessoryserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# limitations under the License.
#
import binascii
import gmpy2
import hashlib
import io
import json
Expand Down Expand Up @@ -970,7 +969,7 @@ def _post_pair_setup(self):
self.log_message('Step #4 /pair-setup')

# 1) use ios pub key to compute shared secret key
ios_pub_key = gmpy2.mpz(binascii.hexlify(d_req[1][1]), 16)
ios_pub_key = int.from_bytes(d_req[1][1], "big")
server = self.server.sessions[self.session_id]['srp']
server.set_client_public_key(ios_pub_key)

Expand All @@ -980,7 +979,7 @@ def _post_pair_setup(self):
self.server.sessions[self.session_id]['session_key'] = session_key

# 2) verify ios proof
ios_proof = gmpy2.mpz(binascii.hexlify(d_req[2][1]), 16)
ios_proof = int.from_bytes(d_req[2][1], "big")
if not server.verify_clients_proof(ios_proof):
d_res.append((TLV.kTLVType_State, TLV.M4, ))
d_res.append((TLV.kTLVType_Error, TLV.kTLVError_Authentication,))
Expand Down
63 changes: 27 additions & 36 deletions homekit/crypto/srp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,18 @@
Implements the Secure Remote Password (SRP) algorithm. More information can be found on
https://tools.ietf.org/html/rfc5054. See HomeKit spec page 36 for adjustments imposed by Apple.
"""

import crypt
import gmpy2
import math
import hashlib

import six
import binascii
import sys


class Srp:
def __init__(self):
# generator as defined by 3072bit group of RFC 5054
self.g = gmpy2.mpz(int(six.b('5'), 16))
self.g = int(b'5', 16)
# modulus as defined by 3072bit group of RFC 5054
self.n = gmpy2.mpz(int(six.b('''\
self.n = int(b'''\
FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08\
8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B\
302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9\
Expand All @@ -48,7 +45,7 @@ def __init__(self):
B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226\
1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C\
BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC\
E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'''), 16))
E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF''', 16)
# HomeKit requires SHA-512 (See page 36)
self.h = hashlib.sha512
self.A = None
Expand All @@ -62,21 +59,22 @@ def generate_private_key():
"""
Static function to generate a 16 byte random key.
:return: the key as gmpy2 multi-precision integer
:return: the key as an integer
"""
return gmpy2.mpz(int(binascii.hexlify(crypt.mksalt(crypt.METHOD_SHA512)[3:].encode()), 16))
private_key = crypt.mksalt(crypt.METHOD_SHA512)[3:].encode()
return int.from_bytes(private_key, "big")

def _calculate_k(self) -> gmpy2.mpz:
def _calculate_k(self) -> int:
# calculate k (see https://tools.ietf.org/html/rfc5054#section-2.5.3)
hash_instance = self.h()
n = Srp.to_byte_array(self.n)
g = bytearray.fromhex((383 * '00' + '05')) # 383 * b'0' + '5'.encode()
hash_instance.update(n)
hash_instance.update(g)
k = gmpy2.mpz(int(binascii.hexlify(hash_instance.digest()), 16))
k = int.from_bytes(hash_instance.digest(), "big")
return k

def _calculate_u(self) -> gmpy2.mpz:
def _calculate_u(self) -> int:
if self.A is None:
raise RuntimeError('Client\'s public key is missing')
if self.B is None:
Expand All @@ -86,21 +84,18 @@ def _calculate_u(self) -> gmpy2.mpz:
B_b = Srp.to_byte_array(self.B)
hash_instance.update(A_b)
hash_instance.update(B_b)
u = gmpy2.mpz(int(binascii.hexlify(hash_instance.digest()), 16))
u = int.from_bytes(hash_instance.digest(), "big")
return u

def get_session_key(self) -> int:
hash_instance = self.h()
hash_instance.update(Srp.to_byte_array(self.get_shared_secret()))
hash_value = int(binascii.hexlify(hash_instance.digest()), 16)
hash_value = int.from_bytes(hash_instance.digest(), "big")
return hash_value

@staticmethod
def to_byte_array(num: int) -> bytearray:
h = gmpy2.digits(num, 16)
if len(h) % 2 == 1:
h = '0' + h
return bytearray.fromhex(h)
return bytearray(num.to_bytes(int(math.ceil(num.bit_length() / 8)), "big"))

def _calculate_x(self) -> int:
i = (self.username + ':' + self.password).encode()
Expand All @@ -112,7 +107,7 @@ def _calculate_x(self) -> int:
hash_instance.update(Srp.to_byte_array(self.salt))
hash_instance.update(hash_value)

return int(binascii.hexlify(hash_instance.digest()), 16)
return int.from_bytes(hash_instance.digest(), "big")

def get_shared_secret(self):
raise NotImplementedError()
Expand All @@ -133,16 +128,16 @@ def __init__(self, username: str, password: str):

def set_salt(self, salt):
if isinstance(salt, bytearray):
self.salt = gmpy2.mpz(salt.hex(), 16)
self.salt = int.from_bytes(salt, "big")
else:
self.salt = salt

def get_public_key(self) -> gmpy2.mpz:
def get_public_key(self):
return pow(self.g, self.a, self.n)

def set_server_public_key(self, B):
if isinstance(B, bytearray):
self.B = gmpy2.mpz(B.hex(), 16)
self.B = int.from_bytes(B, "big")
else:
self.B = B

Expand Down Expand Up @@ -185,19 +180,18 @@ def get_proof(self):
hash_instance.update(Srp.to_byte_array(self.A))
hash_instance.update(Srp.to_byte_array(self.B))
hash_instance.update(K)
r = binascii.hexlify(hash_instance.digest())
return int(r, 16)
return int.from_bytes(hash_instance.digest(), "big")

def verify_servers_proof(self, M):
if isinstance(M, bytearray):
tmp = gmpy2.mpz(M.hex(), 16)
tmp = int.from_bytes(M, "big")
else:
tmp = M
hash_instance = self.h()
hash_instance.update(Srp.to_byte_array(self.A))
hash_instance.update(Srp.to_byte_array(self.get_proof()))
hash_instance.update(Srp.to_byte_array(self.get_session_key()))
return tmp == gmpy2.mpz(binascii.hexlify(hash_instance.digest()), 16)
return tmp == int.from_bytes(hash_instance.digest(), "big")


class SrpServer(Srp):
Expand All @@ -217,14 +211,12 @@ def __init__(self, username, password):
self.A = None

@staticmethod
def _create_salt() -> gmpy2.mpz:
def _create_salt() -> int:
# generate random salt
salt = crypt.mksalt(crypt.METHOD_SHA512)[3:]
salt_b = salt.encode()
salt_hex = binascii.hexlify(salt_b)
salt_int = int(salt_hex, 16)
assert len(salt) == 16
return gmpy2.mpz(salt_int)
salt_b = salt.encode()
return int.from_bytes(salt_b, "big")

def _get_verifier(self) -> int:
hash_value = self._calculate_x()
Expand Down Expand Up @@ -276,14 +268,13 @@ def verify_clients_proof(self, m) -> bool:
hash_instance.update(Srp.to_byte_array(self.A))
hash_instance.update(Srp.to_byte_array(self.B))
hash_instance.update(K)
r = binascii.hexlify(hash_instance.digest())
return m == gmpy2.mpz(r, 16)
return m == int.from_bytes(hash_instance.digest(), "big")

def get_proof(self, m) -> gmpy2.mpz:
def get_proof(self, m) -> int:
hash_instance = self.h()
hash_instance.update(Srp.to_byte_array(self.A))
hash_instance.update(Srp.to_byte_array(m))
hash_instance.update(Srp.to_byte_array(self.get_session_key()))
return gmpy2.mpz(binascii.hexlify(hash_instance.digest()), 16)
return int.from_bytes(hash_instance.digest(), "big")


1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
zeroconf
gmpy2
py25519
hkdf
ed25519
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
],
install_requires=[
'zeroconf',
'gmpy2',
'py25519',
'hkdf',
'ed25519',
Expand Down

0 comments on commit ab52150

Please sign in to comment.