-2

I have this Java code

byte[] decoded= Base64.getDecoder().decode(str.getBytes(StandardCharsets.UTF_8));

byte[] copyfrom12= Arrays.copyOfRange(decoded, 0, Integer.parseInt("12"));
SecretKeySpec secretkeyspec= new SecretKeySpec("0123456789012345".getBytes(), "AES");
            
Cipher cipher= Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(2, secretkeyspec, new GCMParameterSpec(Integer.parseInt("16") * 8, copyfrom12));

return new String(cipher.doFinal(decoded, Integer.parseInt("12"), decoded.length - Integer.parseInt("12")),StandardCharsets.UTF_8);

I thought the above Java code is self-explanatory, it is only opinion ask me if not please

I tried converting to Python, and this is what I tried, but I don't get the expected decrypted output, instead only bytes using the library PyCryptodome and I am getting this error:

ValueError: MAC check failed

key = b'0123456789012345'
cipher = AES.new(key.encode("utf8"), AES.MODE_GCM, nonce=decoded[:12])
plaintext = cipher.decrypt_and_verify(v5,v5[-16:])
print("plaintext: ", plaintext

The plaintext does not contain the expected decrypted value. What am I missing here? Is my conversion is still incomplete? Or am I missing some part?

In the comments, a person says it should be sliced in IV|ciphertext|tag like this, but how can I slice into this?

hanan
  • 532
  • 2
  • 7
  • 23
  • Please clarify "not working". What errors are you getting? What output do you expect? – OneCricketeer Aug 10 '21 at 16:29
  • What is your objective? Question is unclear – AmilaMGunawardana Aug 10 '21 at 16:31
  • Just a note: your code is difficult to read - your variable names should tell what is "inside" and not a "v1" that means cipher, "v2" means SecretKeySpec and so on. Changing the names increases the chance that others try to help you. – Michael Fehr Aug 10 '21 at 16:39
  • Please supply the expected [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) (MRE). We should be able to copy and paste a contiguous block of your code, execute that file, and reproduce your problem along with tracing output for the problem points. This lets us test our suggestions against your test data and desired output. As the posting guidelines tell you, "Make it easy for others to help you". You posted incomplete code with meaningless variable names. This strongly suggests that people who might help you should spend their time elsewhere. – Prune Aug 10 '21 at 16:41
  • @OneCricketeer, I have edited the whole question. thanks – hanan Aug 10 '21 at 17:12
  • @AmilaMGunawardana, I have edited the whole question. thanks – hanan Aug 10 '21 at 17:12
  • @MichaelFehr, I did it now Thanks – hanan Aug 10 '21 at 17:13
  • @Prune, tried my best now, Take a look – hanan Aug 10 '21 at 17:14
  • Hope you find a good answer good luck!!! – AmilaMGunawardana Aug 10 '21 at 17:37
  • 2
    In the Python code, IV, ciphertext and authentication tag must first be separated. Authentication and decryption are to be performed with [`decrypt_and_verify()`](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#gcm-mode) and the parameters passed as in the linked example. – Topaco Aug 10 '21 at 17:42
  • @Topaco, but how can I extract the tag from the java. – hanan Aug 10 '21 at 18:15
  • 1
    `decoded` in the posted Java code is `IV|ciphertext|tag`. The tag size is specified as 16 bytes (the standard value). So the last 16 bytes of `decoded` are the tag. – Topaco Aug 10 '21 at 18:22
  • @Topaco so I can say like `decoded[-16]` ? is that correct? – hanan Aug 10 '21 at 18:34
  • @Topaco I tested it and getting an error `ValueError: MAC check failed` – hanan Aug 10 '21 at 18:41
  • It's `decoded[-16:]`, of course. If you get stuck, please post _complete and consistent_ test data (`decoded` and key) which work with the Java code. – Topaco Aug 10 '21 at 18:50
  • @Topaco when I say slice I meant these `IV|ciphertext|tag` do this data should be sliced before base6 do this data be sliced before base64 data? it should be done after decoding or before? – hanan Aug 11 '21 at 14:17
  • @Topaco , such a legend. Thanks could you take down the link please please :). – hanan Aug 11 '21 at 14:57
  • 1
    I've replaced the original data with test data. The logic itself follows a common pattern and doesn't reveal anything. – Topaco Aug 11 '21 at 15:19
  • @Topaco I think you can post it as answer now so I can mark it as. Thanks :) – hanan Aug 16 '21 at 06:19
  • Sure, I put my comments as an answer. – Topaco Aug 16 '21 at 08:51
  • @Topaco, I tried to build the encryption code and I can encrypt data but when I use the decryption code I got **`MAC check failed`** can you please also write the encryption code for this? – hanan Sep 06 '21 at 12:42
  • Please post your most recent Python code for encryption in a **new** question along with a description of the problem. If necessary, you can link to this post. – Topaco Sep 06 '21 at 12:51

1 Answers1

2

The Java code uses AES-128 in GCM mode. The ciphertext consists of the concatenation of nonce (12 bytes), the actual ciphertext and the authentication tag (16 bytes). The nonce is separated before authentication/decryption. Ciphertext and tag are processed concatenated in the Java code during authentication/decryption. Only if the authentication is successful the decrypted data is returned, otherwise an AEADBadTagException is generated, see doFinal().

PyCryptodome provides the method decrypt_and_verify() for authentication and decryption, which expects ciphertext and tag as separate parameters, unlike the Java side. Only if the authentication is successful the decrypted data is returned, otherwise a ValueError is raised.

Note that PyCryptodome also provides methods to perform authentication and decryption separately: verify() and decrypt(). One conceivable use case is the chunkwise processing of larger ciphertexts when multiple decrypt() calls are required. However, for security reasons, the decrypted data must not be used without prior successful authentication.

In the PyCryptodome code, nonce, ciphertext and tag must first be separated. Then authentication and decryption can be performed. The following PyCryptodome code is a possible implementation of authentication and decryption with PyCryptodome (as one-step and two-step process):

import base64
from Crypto.Cipher import AES

# Separate IV|ciphertext|tag
encryptedDataB64 = 'MDEyMzQ1Njc4OTAxT8G0PHUg00CQ5QYKMxLaHIwrIsy6PIAAA+7mLA9LdXtUftf1Pwv9zFJEHmapbsyfc/HKwxQp1EH3FzA='
encryptedData = base64.b64decode(encryptedDataB64)
nonce = encryptedData[:12]
ciphertext = encryptedData[12:-16]
tag = encryptedData[-16:]

# Authenticate/Decrypt (one step), s. https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#gcm-mode
key = b'0123456789012345'
try:
    cipher = AES.new(key, AES.MODE_GCM, nonce)
    decryptedData = cipher.decrypt_and_verify(ciphertext, tag)
    print(decryptedData) # use authenticated data
except ValueError:
    print('Decryption failed')

#  Authenticate/Decrypt (two step), s. https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html#aes
key = b'0123456789012345'
cipher = AES.new(key, AES.MODE_GCM, nonce)
decryptedData = cipher.decrypt(ciphertext)
try:
    cipher.verify(tag) 
    print(decryptedData) # use authenticated data
except ValueError:
    print('Decryption failed')

with the output:

b'The quick brown fox jumps over the lazy dog'
b'The quick brown fox jumps over the lazy dog'
Topaco
  • 40,594
  • 4
  • 35
  • 62