Skip to content

Commit 5cd3601

Browse files
committed
feat(protocol): Refactoring sha256 authentication code to comply with Python code standards, modifying comments in English
1 parent e39ad30 commit 5cd3601

File tree

3 files changed

+400
-257
lines changed

3 files changed

+400
-257
lines changed

asyncpg/protocol/coreproto.pyx

Lines changed: 10 additions & 257 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import hashlib
99
import hmac
1010
from hashlib import pbkdf2_hmac
1111
include "scram.pyx"
12-
13-
12+
include "sha256.pyx"
1413

1514

1615
cdef dict AUTH_METHOD_NAME = {
@@ -23,248 +22,6 @@ cdef dict AUTH_METHOD_NAME = {
2322
}
2423

2524

26-
cdef hex_string_to_bytes(str hex_string):
27-
"""
28-
将hex字符串转换为bytes,对应Go的hexStringToBytes函数
29-
"""
30-
if not hex_string:
31-
return b''
32-
33-
cdef str upper_string = hex_string.upper()
34-
cdef int bytes_len = len(upper_string) // 2
35-
cdef bytearray result = bytearray(bytes_len)
36-
cdef int i, pos
37-
cdef str high_char, low_char
38-
cdef int high_val, low_val
39-
40-
for i in range(bytes_len):
41-
pos = i * 2
42-
high_char = upper_string[pos]
43-
low_char = upper_string[pos + 1]
44-
45-
# 将字符转换为数值
46-
high_val = "0123456789ABCDEF".index(high_char)
47-
low_val = "0123456789ABCDEF".index(low_char)
48-
49-
result[i] = (high_val << 4) | low_val
50-
51-
return bytes(result)
52-
53-
cdef generate_k_from_pbkdf2(str password, str random64code, int server_iteration):
54-
"""
55-
对应Go的generateKFromPBKDF2函数
56-
注意:Go代码使用的是SHA1,不是SHA256
57-
"""
58-
cdef bytes random32code = hex_string_to_bytes(random64code)
59-
# Go代码使用sha1.New,所以这里使用'sha1'
60-
cdef bytes pwd_encoded = pbkdf2_hmac('sha1', password.encode('utf-8'), random32code, server_iteration, 32)
61-
return pwd_encoded
62-
63-
cdef bytes_to_hex_string(bytes src):
64-
"""
65-
对应Go的bytesToHexString函数
66-
"""
67-
cdef str s = ""
68-
cdef int byte_val, v
69-
cdef str hv
70-
71-
for byte_val in src:
72-
v = byte_val & 0xFF
73-
hv = format(v, 'x')
74-
if len(hv) < 2:
75-
s += "0" + hv
76-
else:
77-
s += hv
78-
return s
79-
80-
cdef get_key_from_hmac(bytes key, bytes data):
81-
"""
82-
对应Go的getKeyFromHmac函数,使用SHA256
83-
"""
84-
cdef object h = hmac.new(key, data, hashlib.sha256)
85-
return h.digest()
86-
87-
cdef get_sha256(bytes message):
88-
"""
89-
对应Go的getSha256函数
90-
"""
91-
cdef object hash_obj = hashlib.sha256()
92-
hash_obj.update(message)
93-
return hash_obj.digest()
94-
95-
cdef get_sm3(bytes message):
96-
"""
97-
对应Go的getSm3函数 (这里用SHA256代替,因为Python标准库没有SM3)
98-
实际项目中需要安装gmssl库来支持SM3
99-
"""
100-
# 注意:这里用SHA256代替SM3,实际使用时需要proper的SM3实现
101-
cdef object hash_obj = hashlib.sha256() # 临时用SHA256代替
102-
hash_obj.update(message)
103-
return hash_obj.digest()
104-
105-
cdef xor_between_password(bytes password1, bytes password2, int length):
106-
"""
107-
对应Go的XorBetweenPassword函数
108-
"""
109-
cdef bytearray result = bytearray(length)
110-
cdef int i
111-
112-
for i in range(length):
113-
result[i] = password1[i] ^ password2[i]
114-
return bytes(result)
115-
116-
cdef bytes_to_hex(bytes source_bytes, bytearray result_array=None, int start_pos=0, int length=-1):
117-
"""
118-
对应Go的bytesToHex函数,支持Java风格的4参数调用
119-
但Go代码只传1个参数,所以做兼容处理
120-
"""
121-
cdef bytes lookup = b'0123456789abcdef'
122-
cdef int pos, i, c, j
123-
cdef int byte_val
124-
cdef bytearray result
125-
126-
if result_array is not None:
127-
# Java风格:4个参数 bytesToHex(hValue, result, 0, hValue.length)
128-
if length == -1:
129-
length = len(source_bytes)
130-
131-
pos = start_pos
132-
133-
for i in range(length):
134-
if i >= len(source_bytes):
135-
break
136-
byte_val = source_bytes[i]
137-
c = int(byte_val & 0xFF)
138-
j = c >> 4
139-
result_array[pos] = lookup[j]
140-
pos += 1
141-
j = c & 0xF
142-
result_array[pos] = lookup[j]
143-
pos += 1
144-
return result_array
145-
else:
146-
# Go风格:1个参数,返回新的bytes
147-
result = bytearray(len(source_bytes) * 2)
148-
pos = 0
149-
150-
for byte_val in source_bytes:
151-
c = int(byte_val & 0xFF)
152-
j = c >> 4
153-
result[pos] = lookup[j]
154-
pos += 1
155-
j = c & 0xF
156-
result[pos] = lookup[j]
157-
pos += 1
158-
159-
return bytes(result)
160-
161-
cpdef rfc5802_algorithm(str password, str random64code, str token, str server_signature="", int server_iteration=4096, str method="sha256"):
162-
"""
163-
RFC5802算法实现,完全对应Go代码逻辑
164-
"""
165-
cdef bytes k, server_key, client_key, stored_key, token_byte
166-
cdef bytes client_signature, hmac_result, h_value
167-
cdef bytearray result
168-
cdef int h_value_len
169-
170-
try:
171-
# Step 1: 生成K (SaltedPassword)
172-
k = generate_k_from_pbkdf2(password, random64code, server_iteration)
173-
174-
# Step 2: 生成ServerKey和ClientKey
175-
server_key = get_key_from_hmac(k, b"Sever Key") # 保持"Sever Key"拼写
176-
client_key = get_key_from_hmac(k, b"Client Key")
177-
178-
# Step 3: 生成StoredKey
179-
if method.lower() == "sha256":
180-
stored_key = get_sha256(client_key)
181-
elif method.lower() == "sm3":
182-
stored_key = get_sm3(client_key)
183-
else:
184-
stored_key = get_sha256(client_key) # 默认使用SHA256
185-
186-
# Step 4: 转换token为bytes
187-
token_byte = hex_string_to_bytes(token)
188-
189-
# Step 5: 计算clientSignature (实际上是ServerSignature,用于验证)
190-
client_signature = get_key_from_hmac(server_key, token_byte)
191-
192-
# Step 6: 验证serverSignature (如果提供)
193-
if server_signature and server_signature != bytes_to_hex_string(client_signature):
194-
return b""
195-
196-
# Step 7: 计算真正的ClientSignature
197-
hmac_result = get_key_from_hmac(stored_key, token_byte)
198-
199-
# Step 8: XOR操作得到ClientProof
200-
h_value = xor_between_password(hmac_result, client_key, len(client_key))
201-
202-
# Step 9: 转换为hex bytes格式 (对应Java的 bytesToHex(hValue, result, 0, hValue.length))
203-
h_value_len = len(h_value)
204-
result = bytearray(h_value_len * 2)
205-
bytes_to_hex(h_value, result, 0, h_value_len)
206-
207-
return bytes(result)
208-
209-
except Exception as e:
210-
raise ValueError(f"RFC5802Algorithm failed: {e}")
211-
212-
213-
214-
215-
216-
217-
218-
219-
220-
221-
def bytes_to_hex(src_bytes, dst_bytes, offset, length):
222-
"""
223-
Java: bytesToHex(byte[] src, byte[] dst, int offset, int length)
224-
- src: 源字节数组
225-
- dst: 目标字节数组
226-
- offset: dst写入起始位置
227-
- length: 需要转换的src字节数量
228-
写入的输出是十六进制ASCII字节(不是16进制数值),每个字节转换成2个字母。
229-
"""
230-
HEX_DIGITS = b'0123456789abcdef'
231-
for i in range(length):
232-
v = src_bytes[i]
233-
if isinstance(v, str):
234-
v = ord(v)
235-
if v < 0:
236-
v += 256
237-
dst_bytes[offset + (i * 2)] = HEX_DIGITS[v >> 4]
238-
dst_bytes[offset + (i * 2) + 1] = HEX_DIGITS[v & 0x0F]
239-
240-
def SHA256_MD5encode(user: bytes, password: bytes, salt: bytes) -> bytes:
241-
try:
242-
md = hashlib.md5()
243-
md.update(password)
244-
md.update(user)
245-
temp_digest = md.digest() # 16 bytes
246-
247-
# hex_digest 70字节(实际前6和后64有效)
248-
hex_digest = bytearray(70)
249-
250-
# 前32个字节为temp_digest的hex(16字节*2)
251-
bytes_to_hex(temp_digest, hex_digest, 0, 16)
252-
253-
# 取前32字节(hex后缀): hex_digest[0:32], 作为SHA256输入
254-
sha = hashlib.sha256()
255-
sha.update(hex_digest[0:32])
256-
sha.update(salt)
257-
pass_digest = sha.digest() # 32 bytes
258-
259-
# pass_digest的hex写到hex_digest[6:]
260-
bytes_to_hex(pass_digest, hex_digest, 6, 32)
261-
262-
# 填入ASCII签名'sha256'
263-
hex_digest[0:6] = b'sha256'
264-
265-
except Exception as e:
266-
raise ValueError('SHA256_MD5encode failed: %s' % str(e))
267-
return bytes(hex_digest)
26825

26926
cdef class CoreProtocol:
27027

@@ -282,6 +39,8 @@ cdef class CoreProtocol:
28239
self.encoding = 'utf-8'
28340
# type of `scram` is `SCRAMAuthentcation`
28441
self.scram = None
42+
# type of `sha256` is `RFC5802Authentication`
43+
self.sha256 = None
28544
# type of `gss_ctx` is `gssapi.SecurityContext` or
28645
# `sspilib.SecurityContext`
28746
self.gss_ctx = None
@@ -814,7 +573,6 @@ cdef class CoreProtocol:
814573
bytes token
815574
int32_t server_iteration
816575
status = self.buffer.read_int32()
817-
818576
if status == AUTH_SUCCESSFUL:
819577
# AuthenticationOk
820578
self.result_type = RESULT_OK
@@ -842,15 +600,15 @@ cdef class CoreProtocol:
842600
'The server requested password-based authentication, '
843601
'but no password was provided.')
844602
if password_stored_method in (SHA256_PASSWORD,PLAIN_PASSWORD):
845-
# 读取认证参数
603+
# Read authentication parameters
846604
random64code = self.buffer.read_bytes(64)
847605
token = self.buffer.read_bytes(8)
848606
server_iteration = self.buffer.read_int32()
849-
# 调用_auth_password_message_sha256生成认证消息
607+
# generate authentication message
850608
self.auth_msg = self._auth_password_message_sha256(random64code, token,
851609
server_iteration)
852610
elif password_stored_method == MD5_PASSWORD:
853-
# MD5密码存储方式
611+
# MD5 password storage method
854612
salt = self.buffer.read_bytes(4)
855613
self.auth_msg = self._auth_password_message_md5(salt)
856614

@@ -938,19 +696,15 @@ cdef class CoreProtocol:
938696

939697
cdef _auth_password_message_sha256(self, bytes random64code, bytes token,
940698
int32_t server_iteration):
941-
"""
942-
处理SHA256认证消息
943-
"""
944699
cdef:
945700
WriteBuffer msg
946701
bytes result
947-
948-
# 调用RFC5802算法计算认证结果
949-
result = rfc5802_algorithm(
702+
self.sha256 = RFC5802Authentication()
703+
result = self.sha256.authenticate(
950704
self.password,
951705
random64code.decode('utf-8'),
952706
token.decode('utf-8'),
953-
'', # salt为空
707+
'',
954708
server_iteration,
955709
'sha256'
956710
)
@@ -959,8 +713,7 @@ cdef class CoreProtocol:
959713
self.result = apg_exc.InterfaceError(
960714
'Invalid username/password, login denied.')
961715
return None
962-
963-
# 构建认证响应消息
716+
964717
msg = WriteBuffer.new_message(b'p')
965718
msg.write_bytes(result)
966719
msg.end_message()

asyncpg/protocol/sha256.pxd

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
# Copyright (C) 2016-present the asyncpg authors and contributors
3+
# <see AUTHORS file>
4+
#
5+
# This module is part of asyncpg and is released under
6+
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
7+
8+
9+
@cython.final
10+
cdef class RFC5802Authentication:
11+
"""Cython header declarations for RFC5802 SCRAM authentication mechanism.
12+
13+
This header file defines the interface for the RFC5802Authentication class
14+
which implements the RFC5802 SCRAM authentication mechanism for secure
15+
password authentication.
16+
17+
The class provides cryptographic operations for:
18+
- PBKDF2 key derivation
19+
- HMAC-based key generation
20+
- SHA256/SM3 hash computation
21+
- Hexadecimal encoding/decoding
22+
- XOR operations for password proofs
23+
24+
All methods follow the RFC5802 specification for SCRAM authentication.
25+
"""
26+
27+
# Class constants
28+
cdef readonly int DEFAULT_ITERATIONS
29+
cdef readonly int DEFAULT_KEY_LENGTH
30+
cdef readonly list SUPPORTED_METHODS
31+
cdef readonly str HEX_CHARS
32+
cdef readonly bytes HEX_LOOKUP
33+
34+
# Hexadecimal conversion methods
35+
cdef bytes hex_string_to_bytes(self, str hex_string)
36+
cdef str _bytes_to_hex_string(self, bytes src)
37+
cdef bytes _bytes_to_hex(self, bytes source_bytes, bytearray result_array=*,
38+
int start_pos=*, int length=*)
39+
40+
# Cryptographic key generation methods
41+
cdef bytes _generate_k_from_pbkdf2(self, str password, str random64code,
42+
int server_iteration)
43+
cdef bytes _get_key_from_hmac(self, bytes key, bytes data)
44+
45+
# Hash computation methods
46+
cdef bytes _get_sha256(self, bytes message)
47+
cdef bytes _get_sm3(self, bytes message)
48+
49+
# Password operation methods
50+
cdef bytes _xor_between_password(self, bytes password1, bytes password2, int length)
51+
52+
# Main authentication method
53+
cpdef bytes authenticate(self, str password, str random64code, str token,
54+
str server_signature=*, int server_iteration=*,
55+
str method=*)

0 commit comments

Comments
 (0)