saro-dat 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
saro_dat/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ from .crypto import DatCryptoAlgorithm, DatCryptoKey
2
+ from .dat import Dat, DatPayload
3
+ from .dat_certificate import DatCertificate
4
+ from .dat_manager import DatManager
5
+ from .signature import DatSignatureAlgorithm, DatSignatureKey, DatSignatureKeyOutOption
6
+
7
+ __all__ = [
8
+ "DatManager",
9
+ "DatCertificate",
10
+ "Dat",
11
+ "DatPayload",
12
+ "DatCryptoKey",
13
+ "DatCryptoAlgorithm",
14
+ "DatSignatureKey",
15
+ "DatSignatureAlgorithm",
16
+ "DatSignatureKeyOutOption",
17
+ ]
saro_dat/crypto.py ADDED
@@ -0,0 +1,79 @@
1
+ import os
2
+ from enum import Enum
3
+ from typing import Union, Dict, Optional
4
+
5
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
6
+
7
+ from .util import decode_base64_url
8
+
9
+
10
+ class DatCryptoAlgorithm(str, Enum):
11
+ AES128GCMN = "AES128GCMN"
12
+ AES256GCMN = "AES256GCMN"
13
+
14
+ CRYPTO_CONFIG: Dict[str, dict] = {
15
+ "AES128GCMN": {"name": "AES-GCM", "length": 16},
16
+ "AES256GCMN": {"name": "AES-GCM", "length": 32},
17
+ }
18
+
19
+ def get_crypto_config(algorithm: str) -> dict:
20
+ config = CRYPTO_CONFIG.get(algorithm)
21
+ if config:
22
+ return config
23
+ raise ValueError(f"Unsupported DAT Crypto Algorithm: {algorithm}")
24
+
25
+ class DatCryptoKey:
26
+ def __init__(self, algorithm: DatCryptoAlgorithm, key_bytes: bytes, config: Optional[Dict[str, dict]] = None):
27
+ if config is None:
28
+ config = get_crypto_config(algorithm)
29
+ self.algorithm = algorithm
30
+ self._config = config
31
+ self._key_bytes = key_bytes
32
+ self._cipher = AESGCM(key_bytes)
33
+
34
+ @classmethod
35
+ def generate(cls, algorithm: DatCryptoAlgorithm) -> DatCryptoKey:
36
+ config = get_crypto_config(algorithm)
37
+ key_bytes = AESGCM.generate_key(bit_length=config['length'] * 8)
38
+ return cls(algorithm, key_bytes, config)
39
+
40
+ @classmethod
41
+ def imports(cls, algorithm: str, raw: bytes) -> DatCryptoKey:
42
+ return cls(DatCryptoAlgorithm(algorithm), raw)
43
+
44
+ def exports(self) -> bytes:
45
+ return self._key_bytes
46
+
47
+ def encrypt(self, data: Union[bytes, str, None]) -> bytes:
48
+ if isinstance(data, str):
49
+ data = data.encode('utf-8')
50
+
51
+ if not data:
52
+ return b""
53
+
54
+ #if self._config["name"] == "AES-GCM":
55
+
56
+ nonce = os.urandom(12)
57
+ ciphertext = self._cipher.encrypt(nonce, data, None)
58
+ return nonce + ciphertext
59
+
60
+ #raise ValueError(f"Unsupported DAT Crypto Algorithm: {self.algorithm}")
61
+
62
+ def decrypt(self, data: Union[bytes, str, None]) -> bytes:
63
+ if isinstance(data, str):
64
+ data = decode_base64_url(data)
65
+
66
+ if not data:
67
+ return b""
68
+
69
+ #if self._config["name"] == "AES-GCM":
70
+
71
+ if len(data) <= 12:
72
+ raise ValueError("Invalid data length")
73
+
74
+ nonce = data[:12]
75
+ ciphertext_with_tag = data[12:]
76
+
77
+ return self._cipher.decrypt(nonce, ciphertext_with_tag, None)
78
+
79
+ #raise ValueError(f"Unsupported DAT Crypto Algorithm: {self.algorithm}")
saro_dat/dat.py ADDED
@@ -0,0 +1,60 @@
1
+ import time
2
+ from typing import Optional, Union
3
+
4
+ from .util import decode_base64_url
5
+
6
+
7
+ class Dat:
8
+ def __init__(self, dat_str: Optional[str]):
9
+ self.dat = dat_str or ''
10
+ self._format = False
11
+ self._expire = 0
12
+ self._cid = 0
13
+ self._plain = b''
14
+ self._secure = b''
15
+ self._signature = b''
16
+
17
+ if self.dat:
18
+ parts = self.dat.split('.')
19
+ if len(parts) == 5:
20
+ try:
21
+ # parts: [expire, cid(hex), plain(b64), secure(b64), signature(b64)]
22
+ self._expire = int(parts[0])
23
+ self._cid = int(parts[1], 16)
24
+ self._plain = decode_base64_url(parts[2])
25
+ self._secure = decode_base64_url(parts[3])
26
+ self._signature = decode_base64_url(parts[4])
27
+ self._format = (len(self._signature) > 0 and self._expire >= 0)
28
+ except (ValueError, RuntimeError):
29
+ self._format = False
30
+
31
+ @classmethod
32
+ def from_value(cls, value: Union[Dat, str, None]) -> Dat:
33
+ if isinstance(value, Dat):
34
+ return value
35
+ return cls(value)
36
+
37
+ def expired(self) -> bool:
38
+ if not self._format:
39
+ return True
40
+ return int(time.time()) > self._expire
41
+
42
+ def body_string(self) -> str:
43
+ """서명 검증을 위한 서명 제외 나머지 본문 반환"""
44
+ if '.' not in self.dat:
45
+ return ""
46
+ return self.dat.rsplit('.', 1)[0]
47
+
48
+ class DatPayload:
49
+ def __init__(self, expire: int, plain: bytes, secure: bytes):
50
+ self.expire = expire
51
+ self.plain_bytes = plain
52
+ self.secure_bytes = secure
53
+
54
+ @property
55
+ def plain(self) -> str:
56
+ return self.plain_bytes.decode('utf-8')
57
+
58
+ @property
59
+ def secure(self) -> str:
60
+ return self.secure_bytes.decode('utf-8')
@@ -0,0 +1,57 @@
1
+ import time
2
+
3
+ from .crypto import DatCryptoKey
4
+ from .signature import DatSignatureKey, DatSignatureKeyOutOption
5
+ from .util import encode_base64_url_str, decode_base64_url
6
+
7
+
8
+ class DatCertificate:
9
+ def __init__(
10
+ self,
11
+ cid: int,
12
+ signature_key: DatSignatureKey,
13
+ crypto_key: DatCryptoKey,
14
+ dat_issue_begin: int,
15
+ dat_issue_end: int,
16
+ dat_ttl: int
17
+ ):
18
+ self.cid = cid
19
+ self._signature_key = signature_key
20
+ self._crypto_key = crypto_key
21
+ self._dat_issue_begin = dat_issue_begin
22
+ self._dat_issue_end = dat_issue_end
23
+ self._dat_ttl = dat_ttl
24
+
25
+ def exports(self, option: DatSignatureKeyOutOption) -> str:
26
+ cid_hex = hex(self.cid)[2:]
27
+ sig_alg = self._signature_key.algorithm.value
28
+ sig_key = self._signature_key.exports(option)
29
+ cry_alg = self._crypto_key.algorithm.value
30
+ cry_key = encode_base64_url_str(self._crypto_key.exports())
31
+
32
+ return f"{cid_hex}.{sig_alg}.{sig_key}.{cry_alg}.{cry_key}.{self._dat_issue_begin}.{self._dat_issue_end}.{self._dat_ttl}"
33
+
34
+ @classmethod
35
+ def imports(cls, format_str: str) -> DatCertificate:
36
+ split = format_str.split(".")
37
+ if len(split) != 8:
38
+ raise ValueError("Invalid Certificate format")
39
+
40
+ cid = int(split[0], 16)
41
+ sig_key = DatSignatureKey.imports(split[1], split[2])
42
+ cry_key = DatCryptoKey.imports(split[3], decode_base64_url(split[4]))
43
+
44
+ return cls(
45
+ cid, sig_key, cry_key,
46
+ int(split[5]), int(split[6]), int(split[7])
47
+ )
48
+
49
+ def issuable(self) -> bool:
50
+ now = int(time.time())
51
+ return self.has_signing_key() and self._dat_issue_begin <= now <= self._dat_issue_end
52
+
53
+ def expired(self) -> bool:
54
+ return int(time.time()) > (self._dat_issue_end + self._dat_ttl)
55
+
56
+ def has_signing_key(self) -> bool:
57
+ return self._signature_key.has_signing_key()
@@ -0,0 +1,115 @@
1
+ import time
2
+ from typing import List, Optional, Union
3
+
4
+ from readerwriterlock import rwlock
5
+
6
+ from . import DatCertificate, Dat, DatPayload
7
+ from .signature import DatSignatureKeyOutOption
8
+ from .util import encode_base64_url_str
9
+
10
+
11
+ class DatManager:
12
+ def __init__(self):
13
+ self._issuer = None
14
+ self._certificates = []
15
+ self._lock = rwlock.RWLockFairD()
16
+
17
+
18
+ def import_certificates(self, input_certs: List[DatCertificate], clear: bool = False):
19
+ certificates = []
20
+
21
+ if not clear:
22
+ with self._lock.gen_rlock():
23
+ certificates.extend(self._certificates)
24
+
25
+ before_cids = set(map(lambda x: x.cid,certificates))
26
+ seen_cids = set()
27
+
28
+ for cert in input_certs:
29
+ if cert.cid in seen_cids:
30
+ raise ValueError(f"Duplicate CID: {cert.cid}")
31
+ seen_cids.add(cert.cid)
32
+ if cert.expired():
33
+ continue
34
+ if cert.cid in before_cids:
35
+ continue
36
+ certificates.append(cert)
37
+
38
+ certificates.sort(key=lambda x: x._dat_issue_end)
39
+ issuer = next((c for c in reversed(certificates) if c.issuable()), None)
40
+
41
+ with self._lock.gen_wlock():
42
+ self._issuer = issuer
43
+ self._certificates = certificates
44
+
45
+
46
+ def imports(self, format_str: str, clear: bool = False):
47
+ certs = []
48
+ for line in format_str.strip().split('\n'):
49
+ if line.strip():
50
+ certs.append(DatCertificate.imports(line.strip()))
51
+ self.import_certificates(certs, clear)
52
+
53
+ def exports(self, option: DatSignatureKeyOutOption) -> str:
54
+ lines = []
55
+
56
+ with self._lock.gen_rlock():
57
+ for cert in self._certificates:
58
+ lines.append(cert.exports(option))
59
+
60
+ return '\n'.join(lines)
61
+
62
+ def _find_unsafe(self, cid: int) -> Optional[DatCertificate]:
63
+ return next((c for c in self._certificates if c.cid == cid), None)
64
+
65
+ def issue(self, plain: Union[bytes, str, None], secure: Union[bytes, str, None]) -> str:
66
+ with self._lock.gen_rlock():
67
+ if self._issuer:
68
+ return self._issue(self._issuer, plain, secure)
69
+ raise RuntimeError("Invalid DAT: Signing Key Does Not Exist")
70
+
71
+ def parse(self, dat_input: Union[Dat, str, None]) -> DatPayload:
72
+ dat = Dat.from_value(dat_input)
73
+ if not dat._format:
74
+ raise ValueError("Invalid DAT: Format")
75
+
76
+ with self._lock.gen_rlock():
77
+ certificate = self._find_unsafe(dat._cid)
78
+ if certificate is not None:
79
+ return self._parse(certificate, dat)
80
+ raise ValueError("Invalid DAT: CID(Certificate ID) Not Found")
81
+
82
+ @staticmethod
83
+ def _issue(cert: DatCertificate, plain: Union[bytes, str, None], secure: Union[bytes, str, None]) -> str:
84
+ now = int(time.time())
85
+ expire = now + cert._dat_ttl
86
+ cid_hex = hex(cert.cid)[2:]
87
+
88
+ # Plain 데이터 처리 (문자열인 경우 utf-8 바이트로)
89
+ plain_bytes = plain.encode() if isinstance(plain, str) else (plain or b'')
90
+ plain_b64 = encode_base64_url_str(plain_bytes)
91
+
92
+ # Secure 데이터 암호화
93
+ encrypted_secure = cert._crypto_key.encrypt(secure)
94
+ secure_b64 = encode_base64_url_str(encrypted_secure)
95
+
96
+ body = f"{expire}.{cid_hex}.{plain_b64}.{secure_b64}"
97
+ signature = encode_base64_url_str(cert._signature_key.sign(body))
98
+
99
+ return f"{body}.{signature}"
100
+
101
+ @staticmethod
102
+ def _parse(cert: DatCertificate, dat_input: Union[Dat, str, None]) -> DatPayload:
103
+ dat = Dat.from_value(dat_input)
104
+ if not dat._format:
105
+ raise RuntimeError("Invalid DAT: Format")
106
+ if dat.expired():
107
+ raise RuntimeError("Invalid DAT: Expired")
108
+
109
+ # 서명 검증
110
+ if not cert._signature_key.verify(dat.body_string(), dat._signature):
111
+ raise RuntimeError("Invalid DAT: Signature")
112
+
113
+ # 데이터 복호화
114
+ decrypted_secure = cert._crypto_key.decrypt(dat._secure)
115
+ return DatPayload(dat._expire, dat._plain, decrypted_secure)
saro_dat/signature.py ADDED
@@ -0,0 +1,174 @@
1
+ from enum import Enum
2
+ from typing import Union, Optional, TypeAlias
3
+
4
+ from cryptography.hazmat.primitives import hashes
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric import ec
7
+ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
8
+ from cryptography.hazmat.primitives.hashes import HashAlgorithm
9
+
10
+ from .util import decode_base64_url, encode_base64_url_str
11
+
12
+
13
+ class DatSignatureAlgorithm(str, Enum):
14
+ P256 = "P256"
15
+ P384 = "P384"
16
+ P521 = "P521"
17
+
18
+ class DatSignatureKeyOutOption(str, Enum):
19
+ FULL = "FULL"
20
+ SIGNING = "SIGNING"
21
+ VERIFYING = "VERIFYING"
22
+
23
+ CurveType: TypeAlias = Union[EllipticCurve]
24
+ HashType: TypeAlias = Union[HashAlgorithm]
25
+ ConfigValue: TypeAlias = dict[str, Union[CurveType, HashAlgorithm]]
26
+
27
+ SIGNATURE_CONFIG: dict[str, ConfigValue] = {
28
+ "P256": {"curve": ec.SECP256R1(), "hash": hashes.SHA256()},
29
+ "P384": {"curve": ec.SECP384R1(), "hash": hashes.SHA384()},
30
+ "P521": {"curve": ec.SECP521R1(), "hash": hashes.SHA512()},
31
+ }
32
+
33
+ def get_signature_config(algorithm: str) -> dict:
34
+ config = SIGNATURE_CONFIG.get(algorithm)
35
+ if config:
36
+ return config
37
+ raise ValueError(f"Unsupported DAT Crypto Algorithm: {algorithm}")
38
+
39
+ class DatSignatureKey:
40
+ def __init__(
41
+ self,
42
+ algorithm: DatSignatureAlgorithm,
43
+ signing_key: Optional[ec.EllipticCurvePrivateKey],
44
+ verifying_key: ec.EllipticCurvePublicKey,
45
+ config: ConfigValue
46
+ ):
47
+ if config is None:
48
+ config = get_signature_config(algorithm)
49
+ self.algorithm = algorithm
50
+ self.signing_key = signing_key
51
+ self.verifying_key = verifying_key
52
+ self._config = config
53
+
54
+ @staticmethod
55
+ def generate(algorithm: Union[DatSignatureAlgorithm, str]) -> DatSignatureKey:
56
+ config = get_signature_config(algorithm)
57
+ if isinstance(algorithm, str):
58
+ algorithm = DatSignatureAlgorithm(algorithm)
59
+ private_key = ec.generate_private_key(config["curve"])
60
+ public_key = private_key.public_key()
61
+ return DatSignatureKey(algorithm, private_key, public_key, config)
62
+
63
+ @staticmethod
64
+ def imports(algorithm: Union[DatSignatureAlgorithm, str], format_str: str) -> DatSignatureKey:
65
+ config = get_signature_config(algorithm)
66
+ if isinstance(algorithm, str):
67
+ algorithm = DatSignatureAlgorithm(algorithm)
68
+
69
+ parts = format_str.split("~")
70
+ parts_len = len(parts)
71
+
72
+ if not (parts_len in range(1, 3)):
73
+ raise ValueError("Invalid DAT Signature Key Format: No keys found")
74
+
75
+ signing_key = None
76
+ verifying_key = None
77
+
78
+ if parts[0]: # 첫 번째 파트가 비어있지 않으면 개인키 존재
79
+ d_value = int.from_bytes(decode_base64_url(parts[0]), 'big')
80
+ signing_key = ec.derive_private_key(d_value, config["curve"])
81
+
82
+ # 2. Public Key (Verifying Key) 처리
83
+ if parts_len == 2 and parts[1]:
84
+ public_bytes = decode_base64_url(parts[1])
85
+ verifying_key = ec.EllipticCurvePublicKey.from_encoded_point(
86
+ config["curve"], public_bytes
87
+ )
88
+ elif signing_key:
89
+ verifying_key = signing_key.public_key()
90
+ else:
91
+ raise ValueError("Invalid DAT Signature Key Format: No keys found")
92
+
93
+ return DatSignatureKey(algorithm, signing_key, verifying_key, config)
94
+
95
+ def exports(self, option: DatSignatureKeyOutOption) -> str:
96
+ rv_parts = ["", ""] # [signing, verifying]
97
+
98
+ if option in ["FULL", "SIGNING"]:
99
+ if self.signing_key:
100
+ # Private Key 'd' 값을 고정된 길이의 바이트로 추출
101
+ private_numbers = self.signing_key.private_numbers()
102
+ curve_size = (self.signing_key.curve.key_size + 7) // 8
103
+ d_bytes = private_numbers.private_value.to_bytes(curve_size, 'big')
104
+ rv_parts[0] = encode_base64_url_str(d_bytes)
105
+ elif option == "SIGNING":
106
+ raise ValueError("Signature key is not supported - verifying only key")
107
+
108
+ if option in ["FULL", "VERIFYING"]:
109
+ # Public Key를 Raw Bytes(Uncompressed)로 추출
110
+ public_bytes = self.verifying_key.public_bytes(
111
+ encoding=serialization.Encoding.X962,
112
+ format=serialization.PublicFormat.UncompressedPoint
113
+ )
114
+ rv_parts[1] = encode_base64_url_str(public_bytes)
115
+
116
+ if option == "SIGNING":
117
+ return rv_parts[0]
118
+ if option == "VERIFYING":
119
+ return "~" + rv_parts[1]
120
+ return f"{rv_parts[0]}~{rv_parts[1]}"
121
+
122
+ def sign(self, body: Union[bytes, str]) -> bytes:
123
+ if not self.signing_key:
124
+ raise ValueError("Signature key is not supported - verifying only key")
125
+
126
+ if isinstance(body, str):
127
+ body = body.encode()
128
+
129
+ if not body:
130
+ raise ValueError("Sign Error - body is empty")
131
+
132
+ signature = self.signing_key.sign(
133
+ body,
134
+ ec.ECDSA(self._config["hash"])
135
+ )
136
+
137
+ return self._der_to_raw_signature(signature)
138
+
139
+ def verify(self, body: Union[bytes, str], signature: Union[bytes, str]) -> bool:
140
+ if isinstance(body, str):
141
+ body = body.encode('utf-8')
142
+ if not body:
143
+ return False
144
+
145
+ sig_bytes = decode_base64_url(signature) if isinstance(signature, str) else signature
146
+
147
+ try:
148
+ # Raw (R|S) -> DER 변환 후 검증
149
+ der_sig = self._raw_to_der_signature(sig_bytes)
150
+ self.verifying_key.verify(
151
+ der_sig,
152
+ body,
153
+ ec.ECDSA(self._config["hash"])
154
+ )
155
+ return True
156
+ except Exception:
157
+ return False
158
+
159
+ def has_signing_key(self) -> bool:
160
+ return self.signing_key is not None
161
+
162
+ # --- 유틸리티: Web Crypto (Raw R|S) <-> OpenSSL (DER) 변환 ---
163
+ def _der_to_raw_signature(self, signature: bytes) -> bytes:
164
+ from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
165
+ r, s = decode_dss_signature(signature)
166
+ size = (self.verifying_key.curve.key_size + 7) // 8
167
+ return r.to_bytes(size, 'big') + s.to_bytes(size, 'big')
168
+
169
+ def _raw_to_der_signature(self, signature: bytes) -> bytes:
170
+ from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
171
+ size = len(signature) // 2
172
+ r = int.from_bytes(signature[:size], 'big')
173
+ s = int.from_bytes(signature[size:], 'big')
174
+ return encode_dss_signature(r, s)
saro_dat/util.py ADDED
@@ -0,0 +1,30 @@
1
+ import base64
2
+ from typing import Union
3
+
4
+
5
+ def encode_base64_url(s: Union[bytes, str, None]) -> bytes:
6
+ if isinstance(s, str):
7
+ if s == "":
8
+ return b""
9
+ s = s.encode('utf-8')
10
+ if s is None:
11
+ return b""
12
+ return base64.urlsafe_b64encode(s).rstrip(b'=')
13
+
14
+ def encode_base64_url_str(s: Union[bytes, str, None]) -> str:
15
+ return encode_base64_url(s).decode('ascii')
16
+
17
+ def decode_base64_url(s: Union[bytes, str, None]) -> bytes:
18
+ if isinstance(s, str):
19
+ if s == "":
20
+ return b""
21
+ s = s.encode('utf-8')
22
+ if s is None:
23
+ return b""
24
+ rem = len(s) % 4
25
+ if rem > 0:
26
+ s += b'=' * (4 - rem)
27
+ return base64.urlsafe_b64decode(s)
28
+
29
+ def decode_base64_url_str(s: Union[bytes, str, None]) -> str:
30
+ return decode_base64_url(s).decode('utf-8')
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: saro-dat
3
+ Version: 1.0.0
4
+ Summary: Distributed Access Token
5
+ Author-email: Marker Seoul <j@saro.me>
6
+ Project-URL: Homepage, https://dat.saro.me
7
+ Project-URL: Repository, https://github.com/saro-lab/dat-pypi
8
+ Project-URL: Documentation, https://dat.saro.me/ko/libs/pypi-saro-dat
9
+ Keywords: dat,distributed,access,token,jwt,security,authentication
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: cryptography>=47.0.0
14
+ Requires-Dist: readerwriterlock>=1.0.9
15
+ Dynamic: license-file
16
+
17
+ # DAT - Distributed Access Token
18
+
19
+ ## Document
20
+
21
+ ### [DAT Run Online](https://dat.saro.me)
22
+
23
+ ### [What is DAT](https://dat.saro.me/--/intro)
24
+
25
+ ### [Example](https://dat.saro.me/--/libs/pypi-saro-dat)
26
+
27
+ ## support signature algorithm
28
+ | name | algorithm |
29
+ |--------|------------|
30
+ | P256 | secp256r1 |
31
+ | P384 | secp384r1 |
32
+ | P521 | secp521r1 |
33
+
34
+ ## support crypto algorithm
35
+ | name | algorithm |
36
+ |------------|-----------------------------|
37
+ | AES128GCMN | aes-128-gcm n(nonce + body) |
38
+ | AES256GCMN | aes-256-cbc n(nonce + body) |
39
+
40
+
41
+ # Performance
42
+ - random plain and secure test
43
+ - mac mini m4 2024 basic (10 core)
44
+ - [test_bench.py](tests/test_bench.py)
45
+ ```
46
+ Plain: A9wAt86DfDVQCXzfijnfB7j5GWk9TOO4lapQS7AzYenPoRAELwaiqKa8IikDmT8FAfNZJFocR66Rvqcae3JcSf3OVIppE0lYDg4G
47
+ Secure: R2KJfHAFClJdS0je4TkU5JoTDfRMGjp5y5zn5sG2iCwsL6IVhjzCOXaVcQPAJwqiEJGDmd3Rdl6tCI0KwAcsjD4ZrqL3IEL5Jr2Q
48
+
49
+ Multi-Thread
50
+ P256 AES128GCMN Issue * 10000 : 192ms
51
+ P256 AES128GCMN Parse * 10000 : 171ms
52
+ P256 AES256GCMN Issue * 10000 : 191ms
53
+ P256 AES256GCMN Parse * 10000 : 168ms
54
+ P384 AES128GCMN Issue * 10000 : 847ms
55
+ P384 AES128GCMN Parse * 10000 : 1841ms
56
+ P384 AES256GCMN Issue * 10000 : 789ms
57
+ P384 AES256GCMN Parse * 10000 : 1829ms
58
+ P521 AES128GCMN Issue * 10000 : 684ms
59
+ P521 AES128GCMN Parse * 10000 : 1355ms
60
+ P521 AES256GCMN Issue * 10000 : 683ms
61
+ P521 AES256GCMN Parse * 10000 : 1343ms
62
+
63
+ Single-Thread
64
+ P256 AES128GCMN Issue * 10000 : 223ms
65
+ P256 AES128GCMN Parse * 10000 : 447ms
66
+ P256 AES256GCMN Issue * 10000 : 213ms
67
+ P256 AES256GCMN Parse * 10000 : 450ms
68
+ P384 AES128GCMN Issue * 10000 : 4915ms
69
+ P384 AES128GCMN Parse * 10000 : 11750ms
70
+ P384 AES256GCMN Issue * 10000 : 4915ms
71
+ P384 AES256GCMN Parse * 10000 : 11705ms
72
+ P521 AES128GCMN Issue * 10000 : 3576ms
73
+ P521 AES128GCMN Parse * 10000 : 7219ms
74
+ P521 AES256GCMN Issue * 10000 : 3623ms
75
+ P521 AES256GCMN Parse * 10000 : 7246ms
76
+ ```
@@ -0,0 +1,12 @@
1
+ saro_dat/__init__.py,sha256=WQAIkMRDxXdWeYP4N_DZZ0t-BFiXbZaoD_OeEdmRS3w,468
2
+ saro_dat/crypto.py,sha256=sWNnfqyeJ4em7CLER2CYmZHI4HLC1wB2giA7A4jU95g,2403
3
+ saro_dat/dat.py,sha256=38i4WFNjFPopbptmidFy9No1w1YwjhOyP3y52xqItkE,1877
4
+ saro_dat/dat_certificate.py,sha256=AoDKEs31ORf7aWcj3LpenBcZtXOD4-vtJ_X04pDNRZY,1969
5
+ saro_dat/dat_manager.py,sha256=B8l7_4cnE53gGdkHUCBJ7S6OcKQJLbW5UicSe85XTxk,4107
6
+ saro_dat/signature.py,sha256=ejJqfHn2yL-pBBiVS1oBoh52OeIlv8Y71Cpgt-id34Q,6620
7
+ saro_dat/util.py,sha256=h7KMrTXU1PaeqzFnMEp5zXA_diBMA2hzr6LYf7nc074,815
8
+ saro_dat-1.0.0.dist-info/licenses/LICENSE,sha256=tdGaI3vE-m4CX8nJv11xod13MG4Mg5Eko3sbOpxkT_E,1065
9
+ saro_dat-1.0.0.dist-info/METADATA,sha256=xCiNRKEX2zanKVgvtIOD7QwtvDKjwIQZQK296enyp-4,2408
10
+ saro_dat-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ saro_dat-1.0.0.dist-info/top_level.txt,sha256=o6vG208t3QETZ0lg6ozgkkbkK0kDcSjVTbDa4dHTw4E,9
12
+ saro_dat-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SARO Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ saro_dat