A Python port of the Invisible Internet Project (I2P)
at main 234 lines 8.3 kB view raw
1"""DSA — Signature type registry, key generator, and sign/verify dispatcher. 2 3Ported from net.i2p.crypto.DSAEngine, net.i2p.crypto.SigType, 4and net.i2p.crypto.KeyGenerator. 5 6Dispatches to the correct algorithm based on SigType: 7- DSA_SHA1 (code 0): Legacy DSA with SHA-1 [keygen only, sign/verify not implemented] 8- ECDSA_SHA256_P256 (code 1): ECDSA with NIST P-256 9- ECDSA_SHA384_P384 (code 2): ECDSA with NIST P-384 10- ECDSA_SHA512_P521 (code 3): ECDSA with NIST P-521 11- EdDSA_SHA512_Ed25519 (code 7): Ed25519 12- RedDSA_SHA512_Ed25519 (code 11): RedDSA (same key format as Ed25519) 13""" 14 15from __future__ import annotations 16 17import enum 18 19from cryptography.hazmat.primitives.asymmetric.ec import ( 20 ECDSA, 21 SECP256R1, 22 SECP384R1, 23 SECP521R1, 24 EllipticCurvePrivateKey, 25 EllipticCurvePublicKey, 26 generate_private_key, 27) 28from cryptography.hazmat.primitives.asymmetric.ed25519 import ( 29 Ed25519PrivateKey, 30 Ed25519PublicKey, 31) 32from cryptography.hazmat.primitives.asymmetric.utils import ( 33 decode_dss_signature, 34 encode_dss_signature, 35) 36from cryptography.hazmat.primitives.hashes import SHA256, SHA384, SHA512 37from cryptography.hazmat.primitives.serialization import ( 38 Encoding, 39 PublicFormat, 40 PrivateFormat, 41 NoEncryption, 42) 43from cryptography.exceptions import InvalidSignature 44 45 46class SigType(enum.Enum): 47 """I2P signature type registry. 48 49 Each member defines the algorithm, key sizes, signature size, 50 and hash algorithm for one signature type code. 51 """ 52 53 # code pubkey_len privkey_len sig_len hash_algo 54 DSA_SHA1 = (0, 128, 20, 40, "sha1") 55 ECDSA_SHA256_P256 = (1, 64, 32, 64, "sha256") 56 ECDSA_SHA384_P384 = (2, 96, 48, 96, "sha384") 57 ECDSA_SHA512_P521 = (3, 132, 66, 132, "sha512") 58 EdDSA_SHA512_Ed25519 = (7, 32, 32, 64, "sha512") 59 RedDSA_SHA512_Ed25519 = (11, 32, 32, 64, "sha512") 60 61 def __init__(self, code: int, pubkey_len: int, privkey_len: int, 62 sig_len: int, hash_algo: str) -> None: 63 self._code = code 64 self._pubkey_len = pubkey_len 65 self._privkey_len = privkey_len 66 self._sig_len = sig_len 67 self._hash_algo = hash_algo 68 69 @property 70 def code(self) -> int: 71 return self._code 72 73 @property 74 def pubkey_len(self) -> int: 75 return self._pubkey_len 76 77 @property 78 def privkey_len(self) -> int: 79 return self._privkey_len 80 81 @property 82 def sig_len(self) -> int: 83 return self._sig_len 84 85 @property 86 def hash_algo(self) -> str: 87 return self._hash_algo 88 89 @classmethod 90 def by_code(cls, code: int) -> SigType | None: 91 """Look up a SigType by its integer code, or None.""" 92 for st in cls: 93 if st.code == code: 94 return st 95 return None 96 97 98_ECDSA_CURVES = { 99 SigType.ECDSA_SHA256_P256: (SECP256R1, SHA256, 32), 100 SigType.ECDSA_SHA384_P384: (SECP384R1, SHA384, 48), 101 SigType.ECDSA_SHA512_P521: (SECP521R1, SHA512, 66), 102} 103 104 105def _ecdsa_sig_to_fixed(der_sig: bytes, component_len: int) -> bytes: 106 """Convert DER-encoded ECDSA signature to fixed-length (r||s).""" 107 r, s = decode_dss_signature(der_sig) 108 return r.to_bytes(component_len, "big") + s.to_bytes(component_len, "big") 109 110 111def _fixed_to_ecdsa_sig(fixed: bytes, component_len: int) -> bytes: 112 """Convert fixed-length (r||s) to DER-encoded ECDSA signature.""" 113 r = int.from_bytes(fixed[:component_len], "big") 114 s = int.from_bytes(fixed[component_len:2 * component_len], "big") 115 return encode_dss_signature(r, s) 116 117 118class KeyGenerator: 119 """Generate key pairs for any SigType.""" 120 121 @staticmethod 122 def generate(sig_type: SigType) -> tuple[bytes, bytes]: 123 """Generate a key pair. 124 125 Args: 126 sig_type: the signature type 127 128 Returns: 129 (public_key_bytes, private_key_bytes) 130 """ 131 if sig_type in (SigType.EdDSA_SHA512_Ed25519, 132 SigType.RedDSA_SHA512_Ed25519): 133 ed_key = Ed25519PrivateKey.generate() 134 pub = ed_key.public_key().public_bytes_raw() 135 priv = ed_key.private_bytes_raw() 136 return pub, priv 137 138 if sig_type in _ECDSA_CURVES: 139 curve_cls, _, _ = _ECDSA_CURVES[sig_type] 140 ec_key = generate_private_key(curve_cls()) 141 # Raw public key: uncompressed point without 0x04 prefix 142 pub_uncompressed = ec_key.public_key().public_bytes( 143 Encoding.X962, PublicFormat.UncompressedPoint 144 ) 145 pub = pub_uncompressed[1:] # strip 0x04 prefix 146 # Extract raw private scalar 147 priv_num = ec_key.private_numbers().private_value 148 priv_bytes = priv_num.to_bytes(sig_type.privkey_len, "big") 149 return pub, priv_bytes 150 151 if sig_type == SigType.DSA_SHA1: 152 from cryptography.hazmat.primitives.asymmetric.dsa import ( 153 generate_parameters, generate_private_key as dsa_gen_key, 154 ) 155 params = generate_parameters(key_size=1024) 156 dsa_key = params.generate_private_key() 157 pub_num = dsa_key.public_key().public_numbers() 158 dsa_priv_num = dsa_key.private_numbers() 159 pub = pub_num.y.to_bytes(128, "big") 160 priv = dsa_priv_num.x.to_bytes(20, "big") 161 return pub, priv 162 163 raise ValueError(f"Unsupported SigType: {sig_type}") 164 165 166class DSAEngine: 167 """Signature dispatcher — routes sign/verify to the correct algorithm.""" 168 169 @staticmethod 170 def sign(data: bytes, private_key: bytes, sig_type: SigType) -> bytes: 171 """Sign data using the specified signature type. 172 173 Args: 174 data: message to sign 175 private_key: raw private key bytes 176 sig_type: signature algorithm 177 178 Returns: 179 Fixed-length signature (sig_type.sig_len bytes) 180 """ 181 if sig_type in (SigType.EdDSA_SHA512_Ed25519, 182 SigType.RedDSA_SHA512_Ed25519): 183 ed_key = Ed25519PrivateKey.from_private_bytes(private_key[:32]) 184 return ed_key.sign(data) 185 186 if sig_type in _ECDSA_CURVES: 187 curve_cls, hash_cls, component_len = _ECDSA_CURVES[sig_type] 188 # Reconstruct private key from raw scalar 189 from cryptography.hazmat.primitives.asymmetric.ec import ( 190 derive_private_key, 191 ) 192 d = int.from_bytes(private_key[:sig_type.privkey_len], "big") 193 ec_key = derive_private_key(d, curve_cls()) 194 der_sig = ec_key.sign(data, ECDSA(hash_cls())) 195 return _ecdsa_sig_to_fixed(der_sig, component_len) 196 197 raise ValueError(f"sign not implemented for {sig_type}") 198 199 @staticmethod 200 def verify(data: bytes, signature: bytes, public_key: bytes, 201 sig_type: SigType) -> bool: 202 """Verify a signature. 203 204 Args: 205 data: message that was signed 206 signature: fixed-length signature bytes 207 public_key: raw public key bytes 208 sig_type: signature algorithm 209 210 Returns: 211 True if signature is valid 212 """ 213 try: 214 if sig_type in (SigType.EdDSA_SHA512_Ed25519, 215 SigType.RedDSA_SHA512_Ed25519): 216 ed_pub = Ed25519PublicKey.from_public_bytes(public_key[:32]) 217 ed_pub.verify(signature, data) 218 return True 219 220 if sig_type in _ECDSA_CURVES: 221 curve_cls, hash_cls, component_len = _ECDSA_CURVES[sig_type] 222 # Reconstruct public key from raw x||y 223 point = b"\x04" + public_key[:sig_type.pubkey_len] 224 ec_pub = EllipticCurvePublicKey.from_encoded_point( 225 curve_cls(), point 226 ) 227 der_sig = _fixed_to_ecdsa_sig(signature, component_len) 228 ec_pub.verify(der_sig, data, ECDSA(hash_cls())) 229 return True 230 231 except (InvalidSignature, ValueError, OverflowError): 232 return False 233 234 raise ValueError(f"verify not implemented for {sig_type}")