Source code for malduck.crypto.rsa
# Copyright (C) 2018 Jurriaan Bremer.
# This file is part of Roach - https://github.com/jbremer/roach.
# See the file 'docs/LICENSE.txt' for copying permission.
import io
from Cryptodome.PublicKey import RSA as RSA_
from itertools import takewhile
from .winhdr import BLOBHEADER, BaseBlob
from ..string.bin import uint32, bigint
from io import BytesIO
from typing import Optional, cast
__all__ = ["PublicKeyBlob", "PrivateKeyBlob", "RSA", "rsa"]
[docs]class PublicKeyBlob(BaseBlob):
magic = b"RSA1"
def __init__(self) -> None:
BaseBlob.__init__(self)
self.e: Optional[int] = None
self.n: Optional[int] = None
def parse(self, buf: BytesIO) -> Optional[int]:
header = buf.read(12)
if len(header) != 12 or header[:4] != self.magic:
return None
self.bitsize = cast(int, uint32(header[4:8], fixed=False))
self.e = cast(int, uint32(header[8:12], fixed=False))
n = buf.read(self.bitsize // 8)
if len(n) != self.bitsize // 8:
return None
self.n = bigint.unpack(n)
return 12 + self.bitsize // 8
def export_key(self) -> bytes:
if not (self.e and self.n):
raise ValueError("The imported key is invalid")
return RSA.export_key(self.n, self.e)
[docs]class PrivateKeyBlob(PublicKeyBlob):
magic = b"RSA2"
def __init__(self) -> None:
PublicKeyBlob.__init__(self)
self.p1: Optional[int] = None
self.p2: Optional[int] = None
self.exp1: Optional[int] = None
self.exp2: Optional[int] = None
self.coeff: Optional[int] = None
self.d: Optional[int] = None
def parse(self, buf: BytesIO) -> None:
off = PublicKeyBlob.parse(self, buf)
if not off:
return
self.p1 = bigint.unpack(buf.read(self.bitsize // 16))
if self.p1 is None:
return
self.p2 = bigint.unpack(buf.read(self.bitsize // 16))
if self.p2 is None:
return
self.exp1 = bigint.unpack(buf.read(self.bitsize // 16))
if self.exp1 is None:
return
self.exp2 = bigint.unpack(buf.read(self.bitsize // 16))
if self.exp2 is None:
return
self.coeff = bigint.unpack(buf.read(self.bitsize // 16))
if self.coeff is None:
return
self.d = bigint.unpack(buf.read(self.bitsize // 8))
if self.d is None:
return
def export_key(self) -> bytes:
if not (self.e and self.n):
raise ValueError("The imported key is invalid")
return RSA.export_key(self.n, self.e, self.d)
BlobTypes = {
6: PublicKeyBlob,
7: PrivateKeyBlob,
}
[docs]class RSA:
algorithms = (0x0000A400,) # RSA
[docs] @staticmethod
def import_key(data: bytes) -> Optional[bytes]:
r"""
Extracts key from buffer containing :class:`PublicKeyBlob` or :class:`PrivateKeyBlob` data
:param data: Buffer with `BLOB` structure data
:type data: bytes
:return: RSA key in PEM format
:rtype: bytes
"""
try:
return RSA_.import_key(data).export_key()
except (ValueError, IndexError):
pass
if len(data) < BLOBHEADER.sizeof():
return None
buf = io.BytesIO(data)
header = BLOBHEADER.parse(buf.read(BLOBHEADER.sizeof()))
if header.bType not in BlobTypes:
return None
if header.aiKeyAlg not in RSA.algorithms:
return None
obj = BlobTypes[header.bType]()
obj.parse(buf)
return obj.export_key()
[docs] @staticmethod
def export_key(
n: int,
e: int,
d: Optional[int] = None,
p: Optional[int] = None,
q: Optional[int] = None,
crt: Optional[int] = None,
) -> bytes:
r"""
Constructs key from tuple of RSA components
:param n: RSA modulus n
:param e: Public exponent e
:param d: Private exponent d
:param p: First factor of n
:param q: Second factor of n
:param crt: CRT coefficient q
:return: RSA key in PEM format
:rtype: bytes
"""
def wrap(x):
return None if x is None else int(x)
tup = wrap(n), wrap(e), wrap(d), wrap(p), wrap(q), wrap(crt)
# PyCryptodome accepts only variable-length tuples
tup_w = tuple(takewhile(lambda x: x is not None, tup))
return RSA_.construct(tup_w, consistency_check=False).export_key() # type: ignore
rsa = RSA