def encrypt(self, P, K, seed=None): r""" Apply the Blum-Goldwasser scheme to encrypt the plaintext ``P`` using the public key ``K``. INPUT: - ``P`` -- a non-empty string of plaintext. The string ``""`` is an empty string, whereas ``" "`` is a string consisting of one white space character. The plaintext can be a binary string or a string of ASCII characters. Where ``P`` is an ASCII string, then ``P`` is first encoded as a binary string prior to encryption. - ``K`` -- a public key, which is the product of two Blum primes. - ``seed`` -- (default: ``None``) if `p` and `q` are Blum primes and `n = pq` is a public key, then ``seed`` is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. If ``seed=None``, then the function would generate its own random quadratic residue in `(\ZZ/n\ZZ)^{\ast}`. Where a value for ``seed`` is provided, it is your responsibility to ensure that the seed is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. OUTPUT: - The ciphertext resulting from encrypting ``P`` using the public key ``K``. The ciphertext `C` is of the form `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Each `c_i` is a sub-block of binary string and `x_{t+1}` is the result of the `t+1`-th iteration of the Blum-Blum-Shub algorithm. ALGORITHM: The Blum-Goldwasser encryption algorithm is described in Algorithm 8.56, page 309 of [MvOV1996]_. The algorithm works as follows: #. Let `n` be a public key, where `n = pq` is the product of two distinct Blum primes `p` and `q`. #. Let `k = \lfloor \log_2(n) \rfloor` and `h = \lfloor \log_2(k) \rfloor`. #. Let `m = m_1 m_2 \cdots m_t` be the message (plaintext) where each `m_i` is a binary string of length `h`. #. Choose a random seed `x_0`, which is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. That is, choose a random `r \in (\ZZ/n\ZZ)^{\ast}` and compute `x_0 = r^2 \bmod n`. #. For `i` from 1 to `t`, do: #. Let `x_i = x_{i-1}^2 \bmod n`. #. Let `p_i` be the `h` least significant bits of `x_i`. #. Let `c_i = p_i \oplus m_i`. #. Compute `x_{t+1} = x_t^2 \bmod n`. #. The ciphertext is `c = (c_1, c_2, \dots, c_t, x_{t+1})`. The value `h` in the algorithm is the sub-block length. If the binary string representing the message cannot be divided into blocks of length `h` each, then other sub-block lengths would be used instead. The sub-block lengths to fall back on are in the following order: 16, 8, 4, 2, 1. EXAMPLES: The following encryption example is taken from Example 8.57, pages 309--310 of [MvOV1996]_. Here, we encrypt a binary string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: p = 499; q = 547; n = p * q sage: P = "10011100000100001100" sage: C = bg.encrypt(P, n, seed=159201); C ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) Convert the ciphertext sub-blocks into a binary string:: sage: bin = BinaryStrings() sage: bin(flatten(C[0])) 00100000110011100100 Now encrypt an ASCII string. The result is random; no seed is provided to the encryption function so the function generates its own random seed:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: K = 32300619509 sage: P = "Blum-Goldwasser encryption" sage: bg.encrypt(P, K) # random ([[1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0], \ [1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1], \ [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], \ [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1], \ [1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0], \ [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1], \ [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0], \ [1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1], \ [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], \ [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1], \ [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1], \ [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0], \ [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1]], 3479653279) TESTS: The plaintext cannot be an empty string. :: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: bg.encrypt("", 3) Traceback (most recent call last): ... ValueError: The plaintext cannot be an empty string. """ # sanity check if P == "": raise ValueError("The plaintext cannot be an empty string.") n = K k = floor(log(n, base=2)) h = floor(log(k, base=2)) bin = BinaryStrings() M = "" try: # the plaintext is a binary string M = bin(P) except TypeError: # encode the plaintext as a binary string # An exception might be raised here if P cannot be encoded as a # binary string. M = bin.encoding(P) # the number of plaintext sub-blocks; each sub-block has length h t = 0 try: # Attempt to use t and h values from the algorithm described # in [MvOV1996]. t = len(M) / h # If the following raises an exception, then we can't use # the t and h values specified by [MvOV1996]. mod(len(M), t) # fall back to using other sub-block lengths except TypeError: # sub-blocks of length h = 16 if mod(len(M), 16) == 0: h = 16 t = len(M) // h # sub-blocks of length h = 8 elif mod(len(M), 8) == 0: h = 8 t = len(M) // h # sub-blocks of length h = 4 elif mod(len(M), 4) == 0: h = 4 t = len(M) // h # sub-blocks of length h = 2 elif mod(len(M), 2) == 0: h = 2 t = len(M) // h # sub-blocks of length h = 1 else: h = 1 t = len(M) // h # If no seed is provided, select a random seed. x0 = seed if seed is None: zmod = IntegerModRing(n) # K = n = pq r = zmod.random_element().lift() while gcd(r, n) != 1: r = zmod.random_element().lift() x0 = power_mod(r, 2, n) # perform the encryption to_int = lambda x: int(str(x)) C = [] for i in range(t): x1 = power_mod(x0, 2, n) p = least_significant_bits(x1, h) # xor p with a sub-block of length h. There are t sub-blocks of # length h each. C.append(list(map(xor, p, [to_int(_) for _ in M[i*h : (i+1)*h]]))) x0 = x1 x1 = power_mod(x0, 2, n) return (C, x1)
def encrypt(self, P, K, seed=None): r""" Apply the Blum-Goldwasser scheme to encrypt the plaintext ``P`` using the public key ``K``. INPUT: - ``P`` -- a non-empty string of plaintext. The string ``""`` is an empty string, whereas ``" "`` is a string consisting of one white space character. The plaintext can be a binary string or a string of ASCII characters. Where ``P`` is an ASCII string, then ``P`` is first encoded as a binary string prior to encryption. - ``K`` -- a public key, which is the product of two Blum primes. - ``seed`` -- (default: ``None``) if `p` and `q` are Blum primes and `n = pq` is a public key, then ``seed`` is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. If ``seed=None``, then the function would generate its own random quadratic residue in `(\ZZ/n\ZZ)^{\ast}`. Where a value for ``seed`` is provided, it is your responsibility to ensure that the seed is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. OUTPUT: - The ciphertext resulting from encrypting ``P`` using the public key ``K``. The ciphertext `C` is of the form `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Each `c_i` is a sub-block of binary string and `x_{t+1}` is the result of the `t+1`-th iteration of the Blum-Blum-Shub algorithm. ALGORITHM: The Blum-Goldwasser encryption algorithm is described in Algorithm 8.56, page 309 of [MenezesEtAl1996]_. The algorithm works as follows: #. Let `n` be a public key, where `n = pq` is the product of two distinct Blum primes `p` and `q`. #. Let `k = \lfloor \log_2(n) \rfloor` and `h = \lfloor \log_2(k) \rfloor`. #. Let `m = m_1 m_2 \cdots m_t` be the message (plaintext) where each `m_i` is a binary string of length `h`. #. Choose a random seed `x_0`, which is a quadratic residue in the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. That is, choose a random `r \in (\ZZ/n\ZZ)^{\ast}` and compute `x_0 = r^2 \bmod n`. #. For `i` from 1 to `t`, do: #. Let `x_i = x_{i-1}^2 \bmod n`. #. Let `p_i` be the `h` least significant bits of `x_i`. #. Let `c_i = p_i \oplus m_i`. #. Compute `x_{t+1} = x_t^2 \bmod n`. #. The ciphertext is `c = (c_1, c_2, \dots, c_t, x_{t+1})`. The value `h` in the algorithm is the sub-block length. If the binary string representing the message cannot be divided into blocks of length `h` each, then other sub-block lengths would be used instead. The sub-block lengths to fall back on are in the following order: 16, 8, 4, 2, 1. EXAMPLES: The following encryption example is taken from Example 8.57, pages 309--310 of [MenezesEtAl1996]_. Here, we encrypt a binary string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: p = 499; q = 547; n = p * q sage: P = "10011100000100001100" sage: C = bg.encrypt(P, n, seed=159201); C ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) Convert the ciphertext sub-blocks into a binary string:: sage: bin = BinaryStrings() sage: bin(flatten(C[0])) 00100000110011100100 Now encrypt an ASCII string. The result is random; no seed is provided to the encryption function so the function generates its own random seed:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: K = 32300619509 sage: P = "Blum-Goldwasser encryption" sage: bg.encrypt(P, K) # random ([[1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0], \ [1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1], \ [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], \ [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1], \ [1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0], \ [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1], \ [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0], \ [1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1], \ [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], \ [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1], \ [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1], \ [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0], \ [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1]], 3479653279) TESTS: The plaintext cannot be an empty string. :: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: bg.encrypt("", 3) Traceback (most recent call last): ... ValueError: The plaintext cannot be an empty string. """ # sanity check if P == "": raise ValueError("The plaintext cannot be an empty string.") n = K k = floor(log(n, base=2)) h = floor(log(k, base=2)) bin = BinaryStrings() M = "" try: # the plaintext is a binary string M = bin(P) except TypeError: # encode the plaintext as a binary string # An exception might be raised here if P cannot be encoded as a # binary string. M = bin.encoding(P) # the number of plaintext sub-blocks; each sub-block has length h t = 0 try: # Attempt to use t and h values from the algorithm described # in [MenezesEtAl1996]. t = len(M) / h # If the following raises an exception, then we can't use # the t and h values specified by [MenezesEtAl1996]. mod(len(M), t) # fall back to using other sub-block lengths except TypeError: # sub-blocks of length h = 16 if mod(len(M), 16) == 0: h = 16 t = len(M) // h # sub-blocks of length h = 8 elif mod(len(M), 8) == 0: h = 8 t = len(M) // h # sub-blocks of length h = 4 elif mod(len(M), 4) == 0: h = 4 t = len(M) // h # sub-blocks of length h = 2 elif mod(len(M), 2) == 0: h = 2 t = len(M) // h # sub-blocks of length h = 1 else: h = 1 t = len(M) // h # If no seed is provided, select a random seed. x0 = seed if seed is None: zmod = IntegerModRing(n) # K = n = pq r = zmod.random_element().lift() while gcd(r, n) != 1: r = zmod.random_element().lift() x0 = power_mod(r, 2, n) # perform the encryption to_int = lambda x: int(str(x)) C = [] for i in xrange(t): x1 = power_mod(x0, 2, n) p = least_significant_bits(x1, h) # xor p with a sub-block of length h. There are t sub-blocks of # length h each. C.append( list(map(xor, p, [to_int(_) for _ in M[i * h:(i + 1) * h]]))) x0 = x1 x1 = power_mod(x0, 2, n) return (C, x1)
def decrypt(self, C, K): r""" Apply the Blum-Goldwasser scheme to decrypt the ciphertext ``C`` using the private key ``K``. INPUT: - ``C`` -- a ciphertext resulting from encrypting a plaintext using the Blum-Goldwasser encryption algorithm. The ciphertext `C` must be of the form `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Each `c_i` is a sub-block of binary string and `x_{t+1}` is the result of the `t+1`-th iteration of the Blum-Blum-Shub algorithm. - ``K`` -- a private key `(p, q, a, b)` where `p` and `q` are distinct Blum primes and `\gcd(p, q) = ap + bq = 1`. OUTPUT: - The plaintext resulting from decrypting the ciphertext ``C`` using the Blum-Goldwasser decryption algorithm. ALGORITHM: The Blum-Goldwasser decryption algorithm is described in Algorithm 8.56, page 309 of [MvOV1996]_. The algorithm works as follows: #. Let `C` be the ciphertext `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Then `t` is the number of ciphertext sub-blocks and `h` is the length of each binary string sub-block `c_i`. #. Let `(p, q, a, b)` be the private key whose corresponding public key is `n = pq`. Note that `\gcd(p, q) = ap + bq = 1`. #. Compute `d_1 = ((p + 1) / 4)^{t+1} \bmod{(p - 1)}`. #. Compute `d_2 = ((q + 1) / 4)^{t+1} \bmod{(q - 1)}`. #. Let `u = x_{t+1}^{d_1} \bmod p`. #. Let `v = x_{t+1}^{d_2} \bmod q`. #. Compute `x_0 = vap + ubq \bmod n`. #. For `i` from 1 to `t`, do: #. Compute `x_i = x_{t-1}^2 \bmod n`. #. Let `p_i` be the `h` least significant bits of `x_i`. #. Compute `m_i = p_i \oplus c_i`. #. The plaintext is `m = m_1 m_2 \cdots m_t`. EXAMPLES: The following decryption example is taken from Example 8.57, pages 309--310 of [MvOV1996]_. Here we decrypt a binary string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: p = 499; q = 547 sage: C = ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) sage: K = bg.private_key(p, q); K (499, 547, -57, 52) sage: P = bg.decrypt(C, K); P [[1, 0, 0, 1], [1, 1, 0, 0], [0, 0, 0, 1], [0, 0, 0, 0], [1, 1, 0, 0]] Convert the plaintext sub-blocks into a binary string:: sage: bin = BinaryStrings() sage: bin(flatten(P)) 10011100000100001100 Decrypt a longer ciphertext and convert the resulting plaintext into an ASCII string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: from sage.crypto.util import bin_to_ascii sage: bg = BlumGoldwasser() sage: p = 78307; q = 412487 sage: K = bg.private_key(p, q) sage: C = ([[1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0], \ ....: [1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1], \ ....: [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], \ ....: [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1], \ ....: [1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0], \ ....: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1], \ ....: [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0], \ ....: [1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1], \ ....: [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], \ ....: [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1], \ ....: [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1], \ ....: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0], \ ....: [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1]], 3479653279) sage: P = bg.decrypt(C, K) sage: bin_to_ascii(flatten(P)) 'Blum-Goldwasser encryption' TESTS: The private key `K = (p, q, a, b)` must be such that `p` and `q` are distinct Blum primes. Even if `p` and `q` pass this criterion, they must also satisfy the requirement `\gcd(p, q) = ap + bq = 1`. :: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: C = ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) sage: K = (7, 7, 1, 2) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: p and q must be distinct Blum primes. sage: K = (7, 23, 1, 2) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: a and b must satisfy gcd(p, q) = ap + bq = 1. sage: K = (11, 29, 8, -3) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: p and q must be distinct Blum primes. """ # ciphertext c = C[0] xt1 = C[-1] # number of ciphertext sub-blocks t = len(c) # length of each ciphertext sub-block h = len(c[0]) # private key p, q, a, b = K # public key n = p * q # sanity checks if p == q: raise ValueError("p and q must be distinct Blum primes.") if (a*p + b*q) != 1: raise ValueError("a and b must satisfy gcd(p, q) = ap + bq = 1.") if (not is_blum_prime(p)) or (not is_blum_prime(q)): raise ValueError("p and q must be distinct Blum primes.") # prepare to decrypt d1 = power_mod((p + 1) // 4, t + 1, p - 1) d2 = power_mod((q + 1) // 4, t + 1, q - 1) u = power_mod(xt1, d1, p) v = power_mod(xt1, d2, q) x0 = mod(v*a*p + u*b*q, n).lift() # perform the decryption M = [] for i in range(t): x1 = power_mod(x0, 2, n) p = least_significant_bits(x1, h) M.append(list(map(xor, p, c[i]))) x0 = x1 return M
def decrypt(self, C, K): r""" Apply the Blum-Goldwasser scheme to decrypt the ciphertext ``C`` using the private key ``K``. INPUT: - ``C`` -- a ciphertext resulting from encrypting a plaintext using the Blum-Goldwasser encryption algorithm. The ciphertext `C` must be of the form `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Each `c_i` is a sub-block of binary string and `x_{t+1}` is the result of the `t+1`-th iteration of the Blum-Blum-Shub algorithm. - ``K`` -- a private key `(p, q, a, b)` where `p` and `q` are distinct Blum primes and `\gcd(p, q) = ap + bq = 1`. OUTPUT: - The plaintext resulting from decrypting the ciphertext ``C`` using the Blum-Goldwasser decryption algorithm. ALGORITHM: The Blum-Goldwasser decryption algorithm is described in Algorithm 8.56, page 309 of [MenezesEtAl1996]_. The algorithm works as follows: #. Let `C` be the ciphertext `C = (c_1, c_2, \dots, c_t, x_{t+1})`. Then `t` is the number of ciphertext sub-blocks and `h` is the length of each binary string sub-block `c_i`. #. Let `(p, q, a, b)` be the private key whose corresponding public key is `n = pq`. Note that `\gcd(p, q) = ap + bq = 1`. #. Compute `d_1 = ((p + 1) / 4)^{t+1} \bmod{(p - 1)}`. #. Compute `d_2 = ((q + 1) / 4)^{t+1} \bmod{(q - 1)}`. #. Let `u = x_{t+1}^{d_1} \bmod p`. #. Let `v = x_{t+1}^{d_2} \bmod q`. #. Compute `x_0 = vap + ubq \bmod n`. #. For `i` from 1 to `t`, do: #. Compute `x_i = x_{t-1}^2 \bmod n`. #. Let `p_i` be the `h` least significant bits of `x_i`. #. Compute `m_i = p_i \oplus c_i`. #. The plaintext is `m = m_1 m_2 \cdots m_t`. EXAMPLES: The following decryption example is taken from Example 8.57, pages 309--310 of [MenezesEtAl1996]_. Here we decrypt a binary string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: p = 499; q = 547 sage: C = ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) sage: K = bg.private_key(p, q); K (499, 547, -57, 52) sage: P = bg.decrypt(C, K); P [[1, 0, 0, 1], [1, 1, 0, 0], [0, 0, 0, 1], [0, 0, 0, 0], [1, 1, 0, 0]] Convert the plaintext sub-blocks into a binary string:: sage: bin = BinaryStrings() sage: bin(flatten(P)) 10011100000100001100 Decrypt a longer ciphertext and convert the resulting plaintext into an ASCII string:: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: from sage.crypto.util import bin_to_ascii sage: bg = BlumGoldwasser() sage: p = 78307; q = 412487 sage: K = bg.private_key(p, q) sage: C = ([[1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0], \ ... [1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1], \ ... [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], \ ... [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1], \ ... [1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0], \ ... [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1], \ ... [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0], \ ... [1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1], \ ... [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], \ ... [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1], \ ... [1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1], \ ... [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0], \ ... [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1]], 3479653279) sage: P = bg.decrypt(C, K) sage: bin_to_ascii(flatten(P)) 'Blum-Goldwasser encryption' TESTS: The private key `K = (p, q, a, b)` must be such that `p` and `q` are distinct Blum primes. Even if `p` and `q` pass this criterion, they must also satisfy the requirement `\gcd(p, q) = ap + bq = 1`. :: sage: from sage.crypto.public_key.blum_goldwasser import BlumGoldwasser sage: bg = BlumGoldwasser() sage: C = ([[0, 0, 1, 0], [0, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], 139680) sage: K = (7, 7, 1, 2) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: p and q must be distinct Blum primes. sage: K = (7, 23, 1, 2) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: a and b must satisfy gcd(p, q) = ap + bq = 1. sage: K = (11, 29, 8, -3) sage: bg.decrypt(C, K) Traceback (most recent call last): ... ValueError: p and q must be distinct Blum primes. """ # ciphertext c = C[0] xt1 = C[-1] # number of ciphertext sub-blocks t = len(c) # length of each ciphertext sub-block h = len(c[0]) # private key p, q, a, b = K # public key n = p * q # sanity checks if p == q: raise ValueError("p and q must be distinct Blum primes.") if (a * p + b * q) != 1: raise ValueError("a and b must satisfy gcd(p, q) = ap + bq = 1.") if (not is_blum_prime(p)) or (not is_blum_prime(q)): raise ValueError("p and q must be distinct Blum primes.") # prepare to decrypt d1 = power_mod((p + 1) // 4, t + 1, p - 1) d2 = power_mod((q + 1) // 4, t + 1, q - 1) u = power_mod(xt1, d1, p) v = power_mod(xt1, d2, q) x0 = mod(v * a * p + u * b * q, n).lift() # perform the decryption M = [] for i in xrange(t): x1 = power_mod(x0, 2, n) p = least_significant_bits(x1, h) M.append(list(map(xor, p, c[i]))) x0 = x1 return M