Skip to content

Commit 1e2eb11

Browse files
committed
feat: added create_hmac_signature function
1 parent b9a0cfd commit 1e2eb11

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

src/apify/_crypto.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
import base64
4+
import hashlib
5+
import hmac
46
from typing import Any
57

68
from cryptography.exceptions import InvalidTag as InvalidTagException
@@ -153,3 +155,37 @@ def decrypt_input_secrets(private_key: rsa.RSAPrivateKey, input_data: Any) -> An
153155
)
154156

155157
return input_data
158+
159+
160+
CHARSET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
161+
162+
163+
def encode_base62(num: int) -> str:
164+
"""Encode the given number to base62."""
165+
if num == 0:
166+
return CHARSET[0]
167+
168+
res = ''
169+
while num > 0:
170+
num, remainder = divmod(num, 62)
171+
res = CHARSET[remainder] + res
172+
return res
173+
174+
175+
# createHmacSignature
176+
@ignore_docs
177+
def create_hmac_signature(secret_key: str, message: str) -> str:
178+
"""Generates an HMAC signature and encodes it using Base62. Base62 encoding reduces the signature length.
179+
180+
Args:
181+
secret_key (str): Secret key used for signing signatures
182+
message (str): Message to be signed
183+
184+
Returns:
185+
str: Base62 encoded signature
186+
"""
187+
signature = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()[:30]
188+
189+
decimal_signature = int(signature, 16)
190+
191+
return encode_base62(decimal_signature)

tests/unit/test_crypto.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
import pytest
66

7-
from apify._crypto import _load_public_key, crypto_random_object_id, load_private_key, private_decrypt, public_encrypt
7+
from apify._crypto import (
8+
_load_public_key,
9+
create_hmac_signature,
10+
crypto_random_object_id,
11+
load_private_key,
12+
private_decrypt,
13+
public_encrypt,
14+
)
815

916
# NOTE: Uses the same keys as in:
1017
# https://github.com/apify/apify-shared-js/blob/master/test/crypto.test.ts
@@ -105,3 +112,20 @@ def test_crypto_random_object_id_length_and_charset() -> None:
105112
long_random_object_id = crypto_random_object_id(1000)
106113
for char in long_random_object_id:
107114
assert char in 'abcdefghijklmnopqrstuvwxyzABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789'
115+
116+
117+
# Check if the method is compatible with js version of the same method in:
118+
# https://github.com/apify/apify-shared-js/blob/master/packages/utilities/src/hmac.ts
119+
def test_create_valid_hmac_signature() -> None:
120+
# This test uses the same secret key and message as in JS tests.
121+
secret_key = 'hmac-secret-key'
122+
message = 'hmac-message-to-be-authenticated'
123+
assert create_hmac_signature(secret_key, message) == 'pcVagAsudj8dFqdlg7mG'
124+
125+
126+
def test_create_same_hmac() -> None:
127+
# This test uses the same secret key and message as in JS tests.
128+
secret_key = 'hmac-same-secret-key'
129+
message = 'hmac-same-message-to-be-authenticated'
130+
for _ in range(5):
131+
assert create_hmac_signature(secret_key, message) == 'FYMcmTIm3idXqleF1Sw5'

0 commit comments

Comments
 (0)