Coretex
rsa.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from typing import Tuple
19 from base64 import b64decode
20 
21 import os
22 
23 from Crypto.PublicKey import RSA
24 from cryptography.hazmat.primitives import hashes, serialization
25 from cryptography.hazmat.primitives.asymmetric import rsa, padding
26 
27 from .random_generator import Random
28 
29 
30 def _generateRsaNumbers(bits: int, seed: bytes) -> Tuple[int, int, int, int, int]:
31  """
32  Randomly generates numbers used for calculating RSA public
33  and private keys
34 
35  Parameters
36  ----------
37  bits : int
38  # of bits in the "n" public number
39  seeed : bytes
40  Array of bytes which represent seed for generating key in a deterministic way.
41  The entropy of the generated key is equal to the entropy of the seed.
42 
43  Returns
44  Tuple[int, int, int, int, int] -> p, q, n, e, d RSA values, e is always equal to 65537
45  """
46 
47  random = Random(seed, hashes.SHA256())
48  numbers = RSA.generate(bits, randfunc = random.getRandomBytes, e = 65537)
49 
50  return numbers.p, numbers.q, numbers.n, numbers.e, numbers.d
51 
52 
53 def generateKey(length: int, seed: bytes) -> rsa.RSAPrivateKey:
54  """
55  Generates RSA key-pair with the provided length and seed
56 
57  Parameters
58  ----------
59  length : int
60  length of the key-pair
61  seed : bytes
62  Array of bytes which represent seed for generating key in a deterministic way.
63  The entropy of the generated key is equal to the entropy of the seed.
64 
65  Returns
66  -------
67  RSAPrivateKey -> cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey object
68  """
69 
70  # p -> large random prime number
71  # q -> large random prime number
72  # n -> p * q
73  # e -> 65537
74  # d -> inverse(e, lcm(p - 1, q - 1))
75  p, q, n, e, d = _generateRsaNumbers(length, seed)
76 
77  dmp1 = rsa.rsa_crt_dmp1(d, p)
78  dmq1 = rsa.rsa_crt_dmq1(d, q)
79  iqmp = rsa.rsa_crt_iqmp(p, q)
80 
81  privateNumbers = rsa.RSAPrivateNumbers(p, q, d, dmp1, dmq1, iqmp, rsa.RSAPublicNumbers(e, n))
82  return privateNumbers.private_key()
83 
84 
85 def getPrivateKeyBytes(key: rsa.RSAPrivateKey) -> bytes:
86  """
87  Converts the provided key into a byte array.
88  Encoding is PEM, Format is PKCS8
89 
90  Parameters
91  ----------
92  key : RSAPrivateKey
93  cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey object
94 
95  Returns
96  -------
97  bytes -> RSA private key serialized to bytes
98  """
99 
100  return key.private_bytes(
101  encoding = serialization.Encoding.PEM,
102  format = serialization.PrivateFormat.PKCS8,
103  encryption_algorithm = serialization.NoEncryption()
104  )
105 
106 
107 def getPublicKeyBytes(key: rsa.RSAPublicKey) -> bytes:
108  """
109  Converts the provided key into a byte array.
110  Encoding is PEM, Format is PKCS1
111 
112  Parameters
113  ----------
114  key : RSAPublicKey
115  cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey object
116 
117  Returns
118  -------
119  bytes -> RSA public key serialized to bytes
120  """
121 
122  return key.public_bytes(
123  encoding = serialization.Encoding.PEM,
124  format = serialization.PublicFormat.PKCS1
125  )
126 
127 
128 def privateKeyFromBytes(keyBytes: bytes) -> rsa.RSAPrivateKey:
129  """
130  Creates private key object from provided private
131  key bytes.
132 
133  Parameters
134  ----------
135  keyBytes : bytes
136  private key bytes
137 
138  Returns
139  -------
140  rsa.RSAPrivateKey -> private key object
141  """
142 
143  key = serialization.load_pem_private_key(keyBytes, password = None)
144 
145  if not isinstance(key, rsa.RSAPrivateKey):
146  raise TypeError
147 
148  return key
149 
150 
151 def getPrivateKey() -> rsa.RSAPrivateKey:
152  """
153  Retrieves RSA private key stored in CTX_PRIVATE_KEY
154  environment variable in base64 format.
155 
156  Returns
157  -------
158  rsa.RSAPrivateKey -> private key object
159  """
160 
161  if "CTX_PRIVATE_KEY" not in os.environ:
162  raise RuntimeError(f"\"CTX_PRIVATE_KEY\" environment variable not set")
163 
164  privateKey = b64decode(os.environ["CTX_PRIVATE_KEY"])
165  return privateKeyFromBytes(privateKey)
166 
167 
168 def decrypt(key: rsa.RSAPrivateKey, value: bytes) -> bytes:
169  """
170  Decrypts ciphertext encrypted using provided key-pair.
171  It is assumed that PKCS#1 padding was used during encryption.
172 
173  Parameters
174  ----------
175  key : rsa.RSAPrivateKey
176  private key object
177  value : bytes
178  ciphertext
179 
180  Returns
181  -------
182  bytes -> decrypted ciphertext
183  """
184 
185  return key.decrypt(value, padding.PKCS1v15())