Skip to content

Commit

Permalink
Implement hmac authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
jotonedev committed Oct 1, 2024
1 parent c3df4e8 commit a318351
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

* [openwebnet documentation](https://developer.legrand.com/Documentation/)
* [old password algorithm](https://rosettacode.org/wiki/OpenWebNet_password#Python)
* [java implementation](https://github.com/mvalla/openwebnet4j/) used in OpenHab developed by [mvalla](https://github.com/mvalla)
* [another python implementation](https://github.com/karel1980/ReOpenWebNet) developed by [karel1980](https://github.com/karel1980)
154 changes: 154 additions & 0 deletions pyown/auth/hmac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from enum import IntEnum

from secrets import token_bytes

from hmac import HMAC, compare_digest
from hashlib import sha1, sha256


__all__ = [
"AuthAlgorithm",
"client_hmac",
"server_hmac",
"compare_hmac",
"create_key",
"hex_to_digits",
]


class AuthAlgorithm(IntEnum):
SHA1 = 1
SHA256 = 2


def client_hmac(
server_key: str,
client_key: str,
password: str,
client_identity: str = "736F70653E",
server_identity: str = "636F70653E",
hash_algorithm: AuthAlgorithm = AuthAlgorithm.SHA256,
) -> bytes:
"""
Generate the HMAC authentication for the client.
Args:
server_key: The key sent by the server (Ra)
client_key: The key generated by the client (Rb)
password: The open password of the server (Kab = sha(kab))
client_identity: string used to identify the client (A)
server_identity: string used to identify the server (B)
hash_algorithm: The hash function to use for the hmac calculation (can be sha1 or sha256)
Returns:
str: the client authentication string in bytes
"""
if hash_algorithm == AuthAlgorithm.SHA1:
hash_function = sha1
elif hash_algorithm == AuthAlgorithm.SHA256:
hash_function = sha256
else:
raise ValueError("Invalid hash algorithm")

kab = hash_function(password.encode()).hexdigest()
hmac_message = f"{server_key}{client_key}{client_identity}{server_identity}{kab}"

hmac = HMAC(
key=server_key.encode(),
msg=hmac_message.encode(),
digestmod=hash_function,
)

return hmac.digest()


def server_hmac(
server_key: str,
client_key: str,
password: str,
hash_algorithm: AuthAlgorithm = AuthAlgorithm.SHA256,
) -> bytes:
"""
Generate the HMAC authentication for the server.
Args:
server_key: The key sent by the server (Ra)
client_key: The key generated by the client (Rb)
password: The open password of the server (Kab = sha(kab))
hash_algorithm: The hash function to use for the hmac calculation (can be sha1 or sha256)
Returns:
str: the server confirmation string in bytes
"""
if hash_algorithm == AuthAlgorithm.SHA1:
hash_function = sha1
elif hash_algorithm == AuthAlgorithm.SHA256:
hash_function = sha256
else:
raise ValueError("Invalid hash algorithm")

kab = hash_function(password.encode()).hexdigest()
hmac_message = f"{client_key}{server_key}{kab}"

hmac = HMAC(
key=server_key.encode(),
msg=hmac_message.encode(),
digestmod=hash_function,
)

return hmac.digest()

def compare_hmac(
hmac1: bytes,
hmac2: bytes,
) -> bool:
"""
Compare two hmacs in constant time.
Args:
hmac1: The first hmac
hmac2: The second hmac
Returns:
bool: True if the hmacs are equal, False otherwise
"""
return compare_digest(hmac1, hmac2)

def create_key(
hash_algorithm: AuthAlgorithm = AuthAlgorithm.SHA256,
) -> str:
"""
Create a random key for the hmac.
Args:
hash_algorithm: The hash function to use for the hmac calculation (can be sha1 or sha256)
Returns:
str: the key in hex format
"""
if hash_algorithm == AuthAlgorithm.SHA1:
hash_function = sha1
elif hash_algorithm == AuthAlgorithm.SHA256:
hash_function = sha256
else:
raise ValueError("Invalid hash algorithm")

return hash_function(token_bytes(32)).hexdigest()

def hex_to_digits(
hex_string: str,
) -> str:
"""
Convert a hex string to digits.
Args:
hex_string: The hex string
Returns:
str: the digits string
"""
out = ""
for c in hex_string:
value = int(c, 16)
out += str(value // 10) + str(value % 10)
return out

0 comments on commit a318351

Please sign in to comment.