1
1
import base64
2
2
import json
3
- import os
3
+ import secrets
4
4
import uuid
5
5
from typing import List , Union
6
6
7
7
import nacl .bindings
8
- from cryptography .hazmat .primitives .asymmetric import ed25519
8
+ import nacl .signing
9
+ from cryptography .hazmat .backends import default_backend
10
+ from cryptography .hazmat .primitives import serialization
11
+ from cryptography .hazmat .primitives .asymmetric import ed25519 , x25519
9
12
from cryptography .hazmat .primitives .ciphers import Cipher , algorithms , modes
13
+ from nacl .encoding import RawEncoder
14
+ from nacl .signing import SigningKey
10
15
11
- from mixinsdk .types .user import UserSession
12
16
from mixinsdk .utils import base64_pad_equal_sign
13
17
14
- from ..types . message import MESSAGE_CATEGORIES
18
+ from ..utils import base64_pad_equal_sign
15
19
16
20
17
21
def parse_message_data (
18
- data : str , category : str , app_session_id : str , app_private_key : bytes
22
+ data_b64_str : str , category : str , app_session_id : str , app_private_key : bytes
19
23
) -> Union [dict , str ]:
20
24
"""
21
- - parse message data (Base64 encoded string) to str or dict
22
- - decrypt message data if category is ENCRYPTED_
25
+ - parse message data to str or dict. if category is ENCRYPTED_*, will decrypt message data first.
23
26
24
27
Returns: data_parsed
25
28
"""
26
- if not data :
29
+ if not data_b64_str :
27
30
return ""
28
31
29
32
if category .startswith ("ENCRYPTED_" ):
30
- d = decrypt_message_data (data , app_session_id , app_private_key )
33
+ d = decrypt_message_data (data_b64_str , app_session_id , app_private_key )
31
34
# print("\ndecrypted:", d)
32
35
else :
33
- d = base64 .b64decode (data ).decode ()
36
+ d = base64 .b64decode (data_b64_str ).decode ()
34
37
35
38
if category .endswith (("_TEXT" , "_POST" )):
36
39
return d
37
40
try :
38
41
return json .loads (d )
39
42
except json .JSONDecodeError :
40
- print (f"Failed to json decode data : { d } " )
43
+ print (f"Failed to json decode data_b64_str : { d } " )
41
44
42
45
43
46
def decrypt_message_data (data_b64_str : str , app_session_id : str , private : bytes ):
44
47
data_bytes = base64 .b64decode (base64_pad_equal_sign (data_b64_str ))
45
- size = 16 + 48 # session id bytes + encrypted key bytes size
48
+ size = 16 + 48 # session id bytes + shared key bytes size
46
49
total = len (data_bytes )
47
- if total < 1 + 2 + 32 + size + 12 :
50
+ # bytes([1]) + session_len + pub_key + session_id + shared_key + nonce + ...data
51
+ if total < 1 + 2 + 32 + 16 + 48 + 12 :
48
52
raise ValueError ("Invalid message data" )
49
53
session_length = int .from_bytes (data_bytes [1 :3 ], byteorder = "little" )
50
54
prefixSize = 35 + session_length * size
@@ -63,113 +67,72 @@ def decrypt_message_data(data_b64_str: str, app_session_id: str, private: bytes)
63
67
block_size = 16
64
68
iv = data_bytes [i + 16 : i + 16 + block_size ]
65
69
key = data_bytes [i + 16 + block_size : i + size ]
66
- cipher = Cipher (algorithms .AES (dst ), modes .CBC (iv ))
67
- encryptor = cipher .decryptor ()
68
- key = encryptor .update (key ) # do not: + encryptor.finalize()
70
+ decryptor = Cipher (algorithms .AES (dst ), modes .CBC (iv )).decryptor ()
71
+ key = decryptor .update (key ) # do not: + decryptor.finalize()
69
72
key = key [:16 ]
70
73
break
71
74
72
75
if len (key ) != 16 :
73
76
raise ValueError ("Invalid key" )
74
77
75
- encrypted_data = data_bytes [prefixSize + 12 : - 16 ] # ? -16 是根据结果错误输出来的,go 的代码没有- 16
78
+ encrypted_data = data_bytes [prefixSize + 12 : - 16 ] # ?len(finalize() + .tag) = 16
76
79
77
80
nonce = data_bytes [prefixSize : prefixSize + 12 ]
78
- cipher = Cipher (algorithms .AES (key ), modes .GCM (nonce ))
79
- encryptor = cipher .decryptor ()
80
- plain_text = encryptor .update (encrypted_data )
81
+ decryptor = Cipher (algorithms .AES (key ), modes .GCM (nonce )).decryptor ()
82
+ plain_text = decryptor .update (encrypted_data )
81
83
return plain_text .decode ()
82
84
83
85
84
86
def encrypt_message_data (
85
- data_b64_str : str , user_sessions : List [UserSession ], app_private_key : bytes
87
+ data_bytes : bytes , user_sessions : List [dict ], app_private_key : bytes
86
88
):
87
- data_bytes = base64 .b64decode (data_b64_str )
88
-
89
89
"""
90
- key := make([]byte, 16)
91
- _, err = rand.Read(key)
92
- if err != nil {
93
- return "", err
94
- }
95
- nonce := make([]byte, 12)
96
- _, err = rand.Read(nonce)
97
- if err != nil {
98
- return "", err
99
- }
100
- block, err := aes.NewCipher(key)
101
- if err != nil {
102
- return "", err
103
- }
104
- aesgcm, err := cipher.NewGCM(block)
105
- if err != nil {
106
- return "", err
107
- }
108
- ciphertext := aesgcm.Seal(nil, nonce, data_bytes, nil)
90
+ session struct: {user_id:uuid str, session_id:uuid str, public_key:str}
109
91
"""
110
- block_size = 16
111
- key = os .urandom (block_size )
112
- nonce = os .urandom (12 )
113
- cipher = Cipher (algorithms .AES (key ), modes .GCM (nonce ))
114
- encryptor = cipher .encryptor ()
115
- ciphertext = encryptor .update (data_bytes ) # do not: + encryptor.finalize()
116
-
117
- session_length = len (user_sessions )
118
- session_length = session_length .to_bytes (2 , byteorder = "little" )
119
-
120
- # private := ed25519.PrivateKey(privateBytes)
121
- # pub, _ := PublicKeyToCurve25519(ed25519.PublicKey(private[32:]))
122
- private = ed25519 .Ed25519PrivateKey ().from_private_bytes (app_private_key )
123
- public = private .public_key ()
124
- priv_curve25519 = nacl .bindings .crypto_sign_ed25519_sk_to_curve25519 (private )
125
- pub_curve25519 = nacl .bindings .crypto_sign_ed25519_sk_to_curve25519 (public )
126
-
127
- session_bytes = bytearray ()
128
- # for _, s := range sessions {
92
+
93
+ key = secrets .token_bytes (16 )
94
+ nonce = secrets .token_bytes (12 )
95
+
96
+ encryptor = Cipher (algorithms .AES (key ), modes .GCM (nonce )).encryptor ()
97
+ main_ciphertext = encryptor .update (data_bytes )
98
+ main_ciphertext += encryptor .finalize () + encryptor .tag
99
+
100
+ # ed25519 private key -> cureve25519 public key
101
+ _pk = nacl .bindings .crypto_sign_ed25519_sk_to_pk (app_private_key )
102
+ curve25519_pubkey = nacl .bindings .crypto_sign_ed25519_pk_to_curve25519 (_pk )
103
+
104
+ # ed25519 private key -> curve25519 private key
105
+ curve25519_privkey = nacl .bindings .crypto_sign_ed25519_sk_to_curve25519 (
106
+ app_private_key
107
+ )
108
+
109
+ session_len = len (user_sessions ).to_bytes (2 , byteorder = "little" )
110
+ sessions_bytes = b""
111
+
112
+ padding = 16 - len (key ) % 16
113
+ pad_text = bytes ([padding ] * padding )
114
+ shared = key + pad_text
115
+
129
116
for s in user_sessions :
130
- # clientPublic, err := base64.RawURLEncoding.DecodeString(s.PublicKey)
131
- client_public = base64 .b64decode (s .public_key )
132
- # var dst, priv, clientPub [32]byte
133
- # copy(clientPub[:], clientPublic[:])
134
- client_pub = client_public [:32 ]
135
-
136
- # curve25519.ScalarMult(&dst, &priv, &clientPub)
137
- dst = nacl .bindings .crypto_scalarmult (priv_curve25519 , client_pub )
138
-
139
- # padding := aes.BlockSize - len(key)%aes.BlockSize
140
- padding = block_size - len (key ) % block_size
141
- padtext = bytes (padding ) * padding
142
- # copy(shared[:], key[:])
143
- shared = key [:]
144
- # shared = append(shared, padtext...)
145
- shared += padtext
146
- # ciphertext := make([]byte, aes.BlockSize+len(shared))
147
- # iv := ciphertext[:aes.BlockSize]
148
- iv = os .urandom (block_size + len (shared ))
149
- # mode := cipher.NewCBCEncrypter(block, iv)
150
- # mode.CryptBlocks(ciphertext[aes.BlockSize:], shared)
151
- # block, err := aes.NewCipher(dst[:])
152
- cipher = Cipher (algorithms .AES (dst ), modes .CBC (iv ))
153
- encryptor = cipher .encryptor ()
154
- ciphertext = encryptor .update (shared )
155
- # id, err := UuidFromString(s.SessionID)
156
- id = uuid .UUID (s .session_id )
157
- # sessionsBytes = append(sessionsBytes, id.Bytes()...)
158
- # sessionsBytes = append(sessionsBytes, ciphertext...)
159
- session_bytes .append (id .bytes )
160
- session_bytes .append (ciphertext )
161
-
162
- # result := []byte{1}
163
- result = bytearray ()
164
- # result = append(result, sessionLen[:]...)
165
- result .append (session_length )
166
- # result = append(result, pub[:]...)
167
- result .append (pub_curve25519 )
168
- # result = append(result, sessionsBytes...)
169
- result .extend (session_bytes )
170
- # result = append(result, nonce[:]...)
171
- result .extend (nonce )
172
- # result = append(result, ciphertext...)
173
- result .extend (ciphertext )
174
- # return base64.RawURLEncoding.EncodeToString(result), nil
175
- return base64 .b64encode (result )
117
+ client_pub = base64 .urlsafe_b64decode (base64_pad_equal_sign (s ["public_key" ]))
118
+ dst = nacl .bindings .crypto_scalarmult (curve25519_privkey , client_pub )
119
+
120
+ iv = secrets .token_bytes (16 )
121
+ encryptor = Cipher (algorithms .AES (dst ), modes .CBC (iv )).encryptor ()
122
+ ciphertext = iv + encryptor .update (shared ) # do not: + encryptor.finalize()
123
+
124
+ id = uuid .UUID (s ["session_id" ]).bytes
125
+ sessions_bytes += id + ciphertext
126
+
127
+ result = (
128
+ bytes ([1 ])
129
+ + session_len
130
+ + curve25519_pubkey
131
+ + sessions_bytes
132
+ + nonce
133
+ + main_ciphertext
134
+ )
135
+ encoded = base64 .urlsafe_b64encode (result ).decode ("utf-8" )
136
+ encoded = encoded .rstrip ("=" ) # remove padding
137
+
138
+ return encoded
0 commit comments