0

I'm using some encryption functions in C# and Java whose output doesn't seem to match. I've been fighting with this for a few days and thought I'd finally turn here for help.

Input string: "a"

Here is a snippet of the java implementation:

CryptoAesGcmService.getInstance().encryptData("a", aes_key)

    import javax.crypto.Cipher;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Base64;
    
    public class CryptoAesGcmService {
    
        private static CryptoAesGcmService instance;
        private static final int GCM_TAG_LENGTH = 16;
        private static final int GCM_IV_LENGTH = 12;
    
        public static CryptoAesGcmService getInstance() {
            if (instance == null) {
                instance = new CryptoAesGcmService();
            }
            return instance;
    
        }
        
        public String encryptData(String plaintext, String key) throws Exception {
            return encrypt2(plaintext, key, createGCMParameter());
        }
        
        private static String encrypt(String plaintext, String key, GCMParameterSpec gcmParameterSpec) throws Exception {
    //      return plaintext;
            try {
                byte[] plainTextByte = plaintext.getBytes(StandardCharsets.UTF_8);
                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
                cipher.init(Cipher.ENCRYPT_MODE, createSecretKeySpec(key), gcmParameterSpec);
                byte[] cipherText = cipher.doFinal(plainTextByte);
                return Base64.getEncoder().encodeToString(cipherText);
            } catch (Exception e) {
                throw new Exception("Unexpected exception occur.", e);
            }
        }
    
        private static SecretKeySpec createSecretKeySpec(String secretKey) throws NoSuchAlgorithmException {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(secretKey.getBytes(StandardCharsets.UTF_8));
            return new SecretKeySpec(md.digest(), "AES");
        }
    
        private static GCMParameterSpec createGCMParameter() {
            return new GCMParameterSpec(GCM_TAG_LENGTH * 8, new byte[GCM_IV_LENGTH]);
        }
        
    }

Output: fix 3UhjsGLxeoCyP/cd7c7p++I=

Here is a snippet of the .Net implementation:

aes.Encrypt("a", aes_key);


using System;
using System.Buffers.Binary;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;

namespace api.Class
{
    public class AESGCM
    {
        private const int KEY_BIT_SIZE = 256;
        private const int MAC_BIT_SIZE = 128;
        private const int NONCE_BIT_SIZE = 128;

        private readonly SecureRandom random;

        private static AESGCM instance;

        public static AESGCM Instance //property of this class. Create an instance if it is not created yet
        {
            get
            {
                if (instance == null)
                    instance = new AESGCM();
                return instance;
            }
        }

        public AESGCM()
        {
            random = new SecureRandom();
        }

        //encrypt with strings
        public string Encrypt(string text, string key, byte[] nonSecretPayload = null)
        {
            if (string.IsNullOrEmpty(text))
                throw new ArgumentException("Text required!", "text");
            //var decodedKey = Convert.FromBase64String(key);
            var decodedKey = Encoding.UTF8.GetBytes(key);
            var plainText = Encoding.UTF8.GetBytes(text);
            var cipherText = EncryptWithKey(plainText, decodedKey, nonSecretPayload);
            return Convert.ToBase64String(cipherText);
        }

        //encrypt with byte array
        private byte[] EncryptWithKey(byte[] text, byte[] key, byte[] nonSecretPayload = null)
        {
            if (key == null || key.Length != KEY_BIT_SIZE / 8)
                throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");

            nonSecretPayload = nonSecretPayload ?? new byte[] { };
            var nonce = new byte[NONCE_BIT_SIZE / 8];
            random.NextBytes(nonce, 0, nonce.Length);
            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
            cipher.Init(true, parameters);
            var cipherText = new byte[cipher.GetOutputSize(text.Length)];
            var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
            cipher.DoFinal(cipherText, len);
            using (var combinedStream = new MemoryStream())
            {
                using (var binaryWriter = new BinaryWriter(combinedStream))
                {
                    binaryWriter.Write(nonSecretPayload);
                    binaryWriter.Write(nonce);
                    binaryWriter.Write(cipherText);
                }
                return combinedStream.ToArray();
            }
        }
    }
}

Output: always changing

example 1 4BNEStJ12YZIQsaFhOcufy1rgXW2/H5kBmPyBSVdf2qP

example 2 z4xNJgr6YLg+lsCA2jUn0HKorN8UXrVm0QtKl10w/Yba

example 3 0IxfAp2vIOmj3fvsJ25VVINHpnaxtZ5KNl89Qk7MNFcn

I want output .net same java. I don't know enough about encryption nor Java to know where to turn next. Any guidance would be greatly appreciated

UPDATE

I try to follow the advice but not working.

using System;
using System.Text;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;

namespace api.Class
{
    public class AESGCM
    {
        private const int KEY_BIT_SIZE = 256;
        private const int MAC_BIT_SIZE = 128;

        //encrypt with strings
        public string Encrypt(string text, string key)
        {
            if (string.IsNullOrEmpty(text))
                throw new ArgumentException("Text required!", "text");

            var decodedKey = Encoding.UTF8.GetBytes(key);
            var plainText = Encoding.UTF8.GetBytes(text);
            var cipherText = EncryptWithKey(plainText, decodedKey);
            return Convert.ToBase64String(cipherText);
        }

        //encrypt with byte array
        private byte[] EncryptWithKey(byte[] text, byte[] key)
        {
            if (key == null || key.Length != KEY_BIT_SIZE / 8)
                throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");

            var nonce = new byte[12];
            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
            cipher.Init(true, parameters);
            var cipherText = new byte[cipher.GetOutputSize(text.Length)];
            var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
            cipher.DoFinal(cipherText, len);

            return cipherText;
        }
    }
}

Output: fix 7szCRlNpTMM+93eTi332Gdw=

FINAL CODE

using System;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;

namespace api.Class
{
    public class AESGCM
    {
        private const int KEY_BIT_SIZE = 256;
        private const int MAC_BIT_SIZE = 128;

        //encrypt with strings
        public string Encrypt(string text, string key)
        {
            if (string.IsNullOrEmpty(text))
                throw new ArgumentException("Text required!", "text");

            var decodedKey = sha256_hash(key);
            var plainText = Encoding.UTF8.GetBytes(text);
            var cipherText = EncryptWithKey(plainText, decodedKey);
            return Convert.ToBase64String(cipherText);
        }

        //encrypt with byte array
        private byte[] EncryptWithKey(byte[] text, byte[] key)
        {
            if (key == null || key.Length != KEY_BIT_SIZE / 8)
                throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");

            var nonce = new byte[12];
            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
            cipher.Init(true, parameters);
            var cipherText = new byte[cipher.GetOutputSize(text.Length)];
            var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
            cipher.DoFinal(cipherText, len);

            return cipherText;
        }

        private byte[] sha256_hash(string value)
        {
            Byte[] result;

            using (var hash = SHA256.Create())
            {
                Encoding enc = Encoding.UTF8;
                result = hash.ComputeHash(enc.GetBytes(value));
            }

            return result;
        }
    }
}

Output: same the java

Thank you very much, @user9014097

  • 1
    With random nonces (as in the C# code) no identical ciphertext can result. For this purpose a constant nonce has to be applied in both codes (of course only for testing purposes). – Topaco Jul 13 '21 at 12:02
  • Different systems have different defaults. With crypto any difference can cause what you are seeing. You need to check *all* the relevant defaults on both systems and explicitly set them so they are the same value. Obviously, key derivation, IV, padding etc. need to be the same. Also you need to check the not so obvious ones, like character encoding. Pick reasonable defaults for everything: PKCS7 padding and UTF-8 encoding for example, and set both sides to the same values. – rossum Jul 13 '21 at 12:04
  • 1
    The C# code seems to consider aad (additional authenticated data) and concatenate data (aad|nonce|ciphertext), which the Java code does not do. Also, the zero IV/nonce in the Java code is generally insecure. – Topaco Jul 13 '21 at 12:16
  • @user9014097 Thanks for the comments! I tried to make output C# same Java. How can I transform Java code to C# .Net code ? – Thanin Keawwichain Jul 13 '21 at 12:53
  • 3
    The C# code is actually more secure (no zero IV) and more practical (takes into account the AAD, uses a strategy for IV transfer). Only the IV/Nonce size of 16 bytes differs from the recommended size of 12 bytes. Also, the Java code uses SHA256 as key derivation which is again insecure (the C# code expects directly the key). To make the C# code functionally equivalent to the Java code: In the C# code, use a zero IV, apply SHA256 for key derivation, remove the concatenation and use a 12 bytes nonce (the first two are security issues). – Topaco Jul 13 '21 at 13:30
  • @user9014097 Beware that there are a lot of arse-hat users that simply take one or two examples [found on e.g. stackoverflow](https://stackoverflow.com/q/65930881/589259) and then let you figure out the differences. A Google search usually let you weed those out. If you find very simple differences such as non-matching parameters and hash algorithms then you kind of know what you can expect. – Maarten Bodewes Jul 13 '21 at 23:40
  • @MarteenBodewes - May be that's the case here, but in dubio pro reo. Anyway, the OP would still have a lot of work to do here. – Topaco Jul 14 '21 at 06:49
  • @user9014097 Thanks for the advice. I was assigned develop api integrate with 3rd Party. This api must encryption requst body. In spec have example encryption in the Java code. I try to follow the advice but not working. I have updated the edits in the post. will be very grateful if you help me to get the solution. – Thanin Keawwichain Jul 14 '21 at 20:14
  • You still need to create the SHA256 hash of `decodedKey` during decryption and use this hash as key. – Topaco Jul 14 '21 at 21:06
  • The results are correct in final code. Thank you very much, @user9014097 – Thanin Keawwichain Jul 15 '21 at 08:26

0 Answers0