B4J Question [solved]Convert Java Code To B4x Code?

behnam_tr

Active Member
Licensed User
Longtime User
Hello, I need to convert a json string to the standard format(normalize Json) requested by the server according to the instructions he said to communicate with a server.
I don't know Java, but I have its Java code. I don't know how to convert to b4x or use inline
Please help friends who know
Thankful
Finally, the function should return a string similar to this string

B4X:
0#2400000#1#0#0#0###1#0#1#1#0000011300#65989672956A1113A0E1CA154229C2E03#2616000#0#19117484910002#19117484910001#1#0#2400000#216000#216000#252544#125036#6037991785693265#1665989670660#19117484910002#2356566#252545


============================================
step 1 : normalize json that return string
step 2 : sign string returned from step1 with privatekey (RSA-PKCS1-v1.5 - 2048 - SHA256)
===========================================

json String:
Dim PacketMap As Map
    PacketMap.Initialize
    PacketMap.Put("uid", Null)
    PacketMap.Put("packetType", "GET_TOKEN")
    PacketMap.Put("retry", False)
    PacketMap.Put("data", CreateMap("username":"test-tsp-id-1"))
    PacketMap.Put("encryptionKeyId", "")
    PacketMap.Put("symmetricKey", "")
    PacketMap.Put("iv", "")
    PacketMap.Put("fiscalId", "")
    PacketMap.Put("dataSignature","")
 
    Dim HeaderMap As Map
    HeaderMap.Initialize
    HeaderMap.Put("requestTraceId",DateTime.Now)
    HeaderMap.Put("timestamp",DateTime.Now)
    HeaderMap.Put("packet",PacketMap)
 
    Dim json As JSONGenerator
    json.Initialize(HeaderMap)
    Log(json.ToPrettyString(1))

Java Code_normalize json:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.Collator;
import java.util.*;
public class CryptoUtils {
   private final static ObjectMapper mapper = new ObjectMapper();
   public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] data = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
         data[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
      }
      return data;
   }
   public static String normalJson(Object object, Map < String, Object > header) {
      if (object == null && header == null) return null;
      Map < String, Object > map = null;
      if (object != null) {
         if (object instanceof String) {
            try {
               object = mapper.readValue((String) object, Object.class);
            } catch (JsonProcessingException e) {
               throw new RuntimeException(e.getMessage());
            }
         }
         if (object instanceof Collection) {
            PacketsWrapper packetsWrapper = new PacketsWrapper((Collection) object);
            map = mapper.convertValue(packetsWrapper, Map.class);
         } else {
            map = mapper.convertValue(object, Map.class);
         }
      }
      if (map == null && header != null) {
         map = header;
      }
      if (map != null && header != null) {
         for (Map.Entry < String, Object > entry: header.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
         }
      }
      Map < String, Object > result = new HashMap < > ();
      flatMap(result, null, map);
      StringBuilder sb = new StringBuilder();
      List < String > keys = new ArrayList < > (result.keySet());
      Collections.sort(keys, Collator.getInstance(Locale.ENGLISH));
      for (String key: keys) {
         String textValue;
         Object value = result.get(key);
         if (value != null) {
            textValue = value.toString();
            if (textValue == null || textValue.equals("")) {
               textValue = "#";
            } else {
               textValue = textValue.replaceAll("#", "##");
            }
         } else {
            textValue = "#";
         }
         sb.append(textValue).append('#');
      }
      return sb.deleteCharAt(sb.length() - 1).toString();
   }
   private static String getKey(String rootKey, String myKey) {
      if (rootKey != null) {
         return rootKey + "." + myKey;
      } else {
         return myKey;
      }
   }
   private static void flatMap(Map < String, Object > result, String rootKey, Object input) {
      if (input instanceof Collection) {
         Collection list = (Collection) input;
         int i = 0;
         for (Object e: list) {
            String key = getKey(rootKey, "E" + i++);
            flatMap(result, key, e);
         }
      } else if (input instanceof Map) {
         Map < String, Object > map = (Map) input;
         for (Map.Entry < String, Object > entry: map.entrySet()) {
            flatMap(result, getKey(rootKey, entry.getKey()), entry.getValue());
         }
      } else {
         result.put(rootKey, input);
      }
   }
   private static class PacketsWrapper {
      private Collection packets;
      public PacketsWrapper() {}
      public PacketsWrapper(Collection packets) {
         this.packets = packets;
      }
      public Collection getPackets() {
         return packets;
      }
      public void setPackets(Collection packets) {
         this.packets = packets;
      }
   }
}


Java_Code_SignString:
public static String getSignedText(String text, String algorithm, PrivateKey privateKey) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
   byte[] data = text.getBytes("UTF8");
   Signature sig = Signature.getInstance(algorithm == null ? " SHA256WITHRSA" : algorithm);
   sig.initSign(privateKey);
   sig.update(data);
   byte[] signatureBytes = sig.sign();
   return Base64.getEncoder().encodeToString(signatureBytes);
}
 

Attachments

  • Normalizeclass.java
    3.5 KB · Views: 33
  • signstring.java
    478 bytes · Views: 29
  • Private.txt
    1.7 KB · Views: 30
Last edited:

Daestrum

Expert
Licensed User
Longtime User
No idea if this outputs what you want, but shows how to call it from B4J using JavaObject (in a non ui for sake of simplicity)
B4X:
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
    ' all from maven
    #AdditionalJar: jackson-core-2.16.2.jar
    #AdditionalJar: jackson-databind-2.16.2.jar
    #AdditionalJar: jackson-annotations-2.16.2.jar
#End Region

Sub Process_Globals
    Dim cryptoutils As JavaObject
End Sub

Sub AppStart (Args() As String)
    cryptoutils.InitializeStatic("b4j.example.main$CryptoUtils") ' this points to the package name where the java is

    Dim PacketMap As Map
    PacketMap.Initialize
    PacketMap.Put("uid", Null)
    PacketMap.Put("packetType", "GET_TOKEN")
    PacketMap.Put("retry", False)
    PacketMap.Put("data", CreateMap("username":"test-tsp-id-1"))
    PacketMap.Put("encryptionKeyId", "")
    PacketMap.Put("symmetricKey", "")
    PacketMap.Put("iv", "")
    PacketMap.Put("fiscalId", "")
    PacketMap.Put("dataSignature","")
 
    Dim HeaderMap As Map
    HeaderMap.Initialize
    HeaderMap.Put("requestTraceId",DateTime.Now)
    HeaderMap.Put("timestamp",DateTime.Now)
    HeaderMap.Put("packet",PacketMap)
 
    Dim json As JSONGenerator
    json.Initialize(HeaderMap)
    Log(json.ToPrettyString(1))
    Log(cryptoutils.RunMethod("normalJson",Array(Null,HeaderMap)))
End Sub
#if java

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import java.text.Collator;
import java.util.*;

public static class CryptoUtils {
   private final static ObjectMapper mapper = new ObjectMapper();
   public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] data = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
         data[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
      }
      return data;
   }
   public static String normalJson(Object object, Map < String, Object > header) {
      if (object == null && header == null) return null;
      Map < String, Object > map = null;
      if (object != null) {
         if (object instanceof String) {
            try {
               object = mapper.readValue((String) object, Object.class);
            } catch (JsonProcessingException e) {
               throw new RuntimeException(e.getMessage());
            }
         }
         if (object instanceof Collection) {
            PacketsWrapper packetsWrapper = new PacketsWrapper((Collection) object);
            map = mapper.convertValue(packetsWrapper, Map.class);
         } else {
            map = mapper.convertValue(object, Map.class);
         }
      }
      if (map == null && header != null) {
         map = header;
      }
      if (map != null && header != null) {
         for (Map.Entry < String, Object > entry: header.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
         }
      }
      Map < String, Object > result = new HashMap < > ();
      flatMap(result, null, map);
      StringBuilder sb = new StringBuilder();
      List < String > keys = new ArrayList < > (result.keySet());
      Collections.sort(keys, Collator.getInstance(Locale.ENGLISH));
      for (String key: keys) {
         String textValue;
         Object value = result.get(key);
         if (value != null) {
            textValue = value.toString();
            if (textValue == null || textValue.equals("")) {
               textValue = "#";
            } else {
               textValue = textValue.replaceAll("#", "##");
            }
         } else {
            textValue = "#";
         }
         sb.append(textValue).append('#');
      }
      return sb.deleteCharAt(sb.length() - 1).toString();
   }
   private static String getKey(String rootKey, String myKey) {
      if (rootKey != null) {
         return rootKey + "." + myKey;
      } else {
         return myKey;
      }
   }
   private static void flatMap(Map < String, Object > result, String rootKey, Object input) {
      if (input instanceof Collection) {
         Collection list = (Collection) input;
         int i = 0;
         for (Object e: list) {
            String key = getKey(rootKey, "E" + i++);
            flatMap(result, key, e);
         }
      } else if (input instanceof Map) {
         Map < String, Object > map = (Map) input;
         for (Map.Entry < String, Object > entry: map.entrySet()) {
            flatMap(result, getKey(rootKey, entry.getKey()), entry.getValue());
         }
      } else {
         result.put(rootKey, input);
      }
   }
   private static class PacketsWrapper {
      private Collection packets;
      public PacketsWrapper() {}
      public PacketsWrapper(Collection packets) {
         this.packets = packets;
      }
      public Collection getPackets() {
         return packets;
      }
      public void setPackets(Collection packets) {
         this.packets = packets;
      }
   }
}

#End If
 
Upvote 1

behnam_tr

Active Member
Licensed User
Longtime User
No idea if this outputs what you want, but shows how to call it from B4J using JavaObject (in a non ui for sake of simplicity)
B4X:
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
    ' all from maven
    #AdditionalJar: jackson-core-2.16.2.jar
    #AdditionalJar: jackson-databind-2.16.2.jar
    #AdditionalJar: jackson-annotations-2.16.2.jar
#End Region

Sub Process_Globals
    Dim cryptoutils As JavaObject
End Sub

Sub AppStart (Args() As String)
    cryptoutils.InitializeStatic("b4j.example.main$CryptoUtils") ' this points to the package name where the java is

    Dim PacketMap As Map
    PacketMap.Initialize
    PacketMap.Put("uid", Null)
    PacketMap.Put("packetType", "GET_TOKEN")
    PacketMap.Put("retry", False)
    PacketMap.Put("data", CreateMap("username":"test-tsp-id-1"))
    PacketMap.Put("encryptionKeyId", "")
    PacketMap.Put("symmetricKey", "")
    PacketMap.Put("iv", "")
    PacketMap.Put("fiscalId", "")
    PacketMap.Put("dataSignature","")
 
    Dim HeaderMap As Map
    HeaderMap.Initialize
    HeaderMap.Put("requestTraceId",DateTime.Now)
    HeaderMap.Put("timestamp",DateTime.Now)
    HeaderMap.Put("packet",PacketMap)
 
    Dim json As JSONGenerator
    json.Initialize(HeaderMap)
    Log(json.ToPrettyString(1))
    Log(cryptoutils.RunMethod("normalJson",Array(Null,HeaderMap)))
End Sub
#if java

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import java.text.Collator;
import java.util.*;

public static class CryptoUtils {
   private final static ObjectMapper mapper = new ObjectMapper();
   public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] data = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
         data[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
      }
      return data;
   }
   public static String normalJson(Object object, Map < String, Object > header) {
      if (object == null && header == null) return null;
      Map < String, Object > map = null;
      if (object != null) {
         if (object instanceof String) {
            try {
               object = mapper.readValue((String) object, Object.class);
            } catch (JsonProcessingException e) {
               throw new RuntimeException(e.getMessage());
            }
         }
         if (object instanceof Collection) {
            PacketsWrapper packetsWrapper = new PacketsWrapper((Collection) object);
            map = mapper.convertValue(packetsWrapper, Map.class);
         } else {
            map = mapper.convertValue(object, Map.class);
         }
      }
      if (map == null && header != null) {
         map = header;
      }
      if (map != null && header != null) {
         for (Map.Entry < String, Object > entry: header.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
         }
      }
      Map < String, Object > result = new HashMap < > ();
      flatMap(result, null, map);
      StringBuilder sb = new StringBuilder();
      List < String > keys = new ArrayList < > (result.keySet());
      Collections.sort(keys, Collator.getInstance(Locale.ENGLISH));
      for (String key: keys) {
         String textValue;
         Object value = result.get(key);
         if (value != null) {
            textValue = value.toString();
            if (textValue == null || textValue.equals("")) {
               textValue = "#";
            } else {
               textValue = textValue.replaceAll("#", "##");
            }
         } else {
            textValue = "#";
         }
         sb.append(textValue).append('#');
      }
      return sb.deleteCharAt(sb.length() - 1).toString();
   }
   private static String getKey(String rootKey, String myKey) {
      if (rootKey != null) {
         return rootKey + "." + myKey;
      } else {
         return myKey;
      }
   }
   private static void flatMap(Map < String, Object > result, String rootKey, Object input) {
      if (input instanceof Collection) {
         Collection list = (Collection) input;
         int i = 0;
         for (Object e: list) {
            String key = getKey(rootKey, "E" + i++);
            flatMap(result, key, e);
         }
      } else if (input instanceof Map) {
         Map < String, Object > map = (Map) input;
         for (Map.Entry < String, Object > entry: map.entrySet()) {
            flatMap(result, getKey(rootKey, entry.getKey()), entry.getValue());
         }
      } else {
         result.put(rootKey, input);
      }
   }
   private static class PacketsWrapper {
      private Collection packets;
      public PacketsWrapper() {}
      public PacketsWrapper(Collection packets) {
         this.packets = packets;
      }
      public Collection getPackets() {
         return packets;
      }
      public void setPackets(Collection packets) {
         this.packets = packets;
      }
   }
}

#End If

Thank you for your kindness.I tested the code and it works correctly
After this step, the generated string must be signed by the private key (last step!!)
I will add the Java code of this case to the first post so that there is no need to create an additional topic
Thank you for taking a look at it
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
the 2nd half of the puzzle works like this:
B4X:
    jo = Me  
    Dim privatekey As String = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCbQxh5MndksiaO" _
& "KZjLs/Ps9DJYNQlwvQA2Qa7gf1AP4aQRdNQR1tThTH2vc2+rdveZ9rWnS+8gb3AC" _
& "AvHk5t2NQF347v3k5Lh+b/t/AQtEsUzp7kspna8vUNHZnPJpcvpWocodQcZZu9B8" _
& "iTdOiJjJxLgivTn87p9LIA4uOr15sm6hT5CB8o+rm+Q2EB0lAIUFhqcFKe/MMf0X" _
& "CyhLlGtjo/nNDf+f9bLHu0wVqmEFS5QFin3+ANvRJcYnQDxkfLWTP8Xa+ryq7xPh" _
& "2TDWf5X3keWE8/8yeEfi/fzx8pSz9zVr5VhAZ42MFE5Z+rHb3ISx0hcpZ9B3iXj1" _
& "UTJmjVkjAgMBAAECggEAPTpB6fWNYM/iZXig8V/nRx/rxJ4xuVxHO54+bPi35XNp" _
& "pJqqUNij/a8Q94Ix3O/e58ADVdSbmfJoPNrVcpvabewewt7YkA3kdbQKfUS3oWC8" _
& "AJqA2In+/k1EXClI7W2yrDxEPLJmGpf2uU3RWi6C9jqWFNkDh6vAdLtQ7Eks1FL4" _
& "tao4cdWAz23U9VqoYy1SSlUg1UVrLF0jePyp1R4YEFeqfDwHAvY6CpDocWdOAW66" _
& "YnASq5Z0wLy1jrkE8BrpMdMvss1FxgkmzJMkr53vrQ6XRTE2QqODevhvQdrCEejd" _
& "kGLkW3Cy3+rIOpqaeu5Se/dQ7nmSpYDd/LWjVKI3uQKBgQD4q4+rWyHTNQO4yl+w" _
& "sEWTLTS0JodY4BF6JWChl4Frku1KsXNOqgE2BGSiv29U6WJcDE1luwh9wU2T9/1n" _
& "4mlMy1hJ8qgUzVhIIgKHAr7FealnO7Zt/PV9c4LoEHRl/BaitLEenCbj9n+aXwsj" _
& "GmngkbmvW5Jcp1ykNbu9qQIR2QKBgQCf1q/ctjEI9pr6mZ6MBq8nFHx9oWHyjexw" _
& "wac/0U+LUY0CPqtTLnkNHc4UsYGk2lM31VExFn+oKs1rxndWRwgAEY/bDxFwFxcy" _
& "O7nTgak3hJYzc/Zv4kDatIrDtpFV18nBnDjeTj0QTBJFtaOoHjqJrNQGTCHrvK+l" _
& "kXaKdxdpWwKBgQCrwzUVk3klvjS363F1RgyIwGzrEsHibcMkr7SzaUcH2xD0yuVu" _
& "rJbxjM9Gaxyndh1un0DGyA3xbxf64Qy0OPurA7oUOfxHgh88k+FTCF5lYMfWerRj" _
& "/JpE8Qi26sa7uwiXkl/VWN60D4vMQWIb+R6w83di3MmYHjIrasInGxpG4QKBgQCF" _
& "9tnOkqTmbknWX4qSscd7aaAta2U8ddcFaklTI4sXqXIVv5C4Vur+I0zl6yBNmu7E" _
& "jPVPvxufRsCE5AKBPWdnJ1D6uNZUrAW1BHnq23GIJof89+REQc05gLgM8Kc+220t" _
& "6FGBgPGNBzUJWAOilDNb0I6j+Is1mR9eFVzVEJt+oQKBgQC8Fs7az7mhv00ffxAD" _
& "ehc2w9KS7vrF9FVSSiFzv+QtaHCHnGrQMepAomk8DHfT4g+FJIJyZLs89qFugTYR" _
& "aozcVkxASaPkx8TljYLRs7oKu68+JyCLxlAJRuw8qO9PV0fYiqurOWBdspVmjcBl" _
& "4vP0nmpHoNfeaJMY8PF/cB2hzA=="

    jo.RunMethod("getSignedText", Array("SOME_NORMALIZED_TEXT",Null,privatekey))

    ' in case you get an "algorithn not found error", this call below will tell
    ' you what's available on your computer.
    ' jo.RunMethod("whatsAvailable",Null)

then:
B4X:
#if Java
import java.security.Signature;
import java.util.Base64;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.util.Arrays;
import java.security.Provider.Service;

public static String getSignedText(String text, String algorithm, String privateKey) {
    try {
   
        // convert privateKey string to PrivateKey.class
       
        byte[] clear = Base64.getDecoder().decode(privateKey.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clear);
        KeyFactory fact = KeyFactory.getInstance("RSA");
        PrivateKey priv = fact.generatePrivate(keySpec);
        Arrays.fill(clear, (byte) 0);
       
       
        byte[] data = text.getBytes("UTF8");
           Signature sig = Signature.getInstance(algorithm == null ? "SHA256withRSA" : algorithm);
           sig.initSign(priv);
           sig.update(data);
           byte[] signatureBytes = sig.sign();
           String retString = Base64.getEncoder().encodeToString(signatureBytes);
           BA.Log(retString);
           return(retString);
    }
    catch(Exception e) {
        BA.Log(e.toString());
        return(e.toString());
    }
}

    import java.util.TreeSet;
    import java.security.Security;
    import java.security.Provider;
       
    public static void whatsAvailable() {
        TreeSet<String> algorithms = new TreeSet<>();
        for (Provider provider : Security.getProviders())
            for (Service service : provider.getServices())
                if (service.getType().equals("Signature"))
                    algorithms.add(service.getAlgorithm());

        for (String algorithm : algorithms)
            BA.Log(algorithm);
    }
#End If

notes:
1) this line of java code you originally posted is wrong:
Signature sig = Signature.getInstance(algorithm == null ? " SHA256WITHRSA" : algorithm);
there is no space before SHA256WITHRSA (" SHA256WITHRSA"), and "SHA256WITHRSA" itself is SHA256withRSA

2) your private key is a string which has to be converted to an instance of PrivateKey before it can be signed
3) your private key does includes the header and trailer. they have to be stripped before using the key for signing. i did that in the b4j code. but it could be done in the inline java.
4) the result is "similar" to what you're looking for. where what you're expecting has "#"'s, my version has "+". i don't know why the difference, but it is simple enough to subsititue "#" for "+" in the result (assuming the rest was what you're expecting).
when i ran the code with a random string, i got: L+0Kka+ALrt8ues2lAx2Ad+MVjbGfRJ8cOKtPTM5sC46wG1aUbWkMQHUDZbT9bwYGF+isemhncbkcL+SS4GKuPyVOX5AR7rfX44br+GP/eZFzQwFvwJDtjoCY5vm6cpKyLwbKiynNUttEoqYQGf1qfhJXFtxH2As1F4rcRbfM/pFmJtELgGTKotYhjZZnZQ7lPPOT/URGn3VUBuOUzFl9laiJbiYow/TvzZT1iK/f2mX94yd9yPczSI24alSHLkwJgfhiBqSON+9XPFnUH+BNsbK/YrDIp6BCW+lIFwTPUhYJTcuaghQqLm9hJQZ/vSUHeu6RP2PYFSWIU6R3eSzgg==

also, your expected output is a hex string. what i got using the code snippet you posted is base64. i don't know if there is a missing step in the snippet. since there was already an error in one line, i suppose it's possible.
5) not all algorithms are supported on all devices and in all versions of java. in such occasions, you get an exception, eg, "algorithm not found..." before i noticed the error in the snippet, i got that exception. in order to know which algorithms a device supports, i added another inline routine. if you run it, it spits out a list of all the algorithms supported on your device. when i ran it, i got a list:

MD2withRSA
MD5andSHA1withRSA
MD5withRSA
NONEwithDSA
NONEwithECDSA
NONEwithRSA
SHA1withDSA
SHA1withECDSA
SHA1withRSA
SHA224withDSA
SHA224withECDSA
SHA224withRSA
SHA256withDSA
SHA256withECDSA
SHA256withRSA
SHA384withECDSA
SHA384withRSA
SHA512withECDSA
SHA512withRSA

when i saw "SHA256withRSA", i realized there was a misspelling in the java snippet.

anyway, plug the 2 snippets posted above into your b4j code and see what you get when you use your private key
 
Upvote 1

behnam_tr

Active Member
Licensed User
Longtime User
the 2nd half of the puzzle works like this:
B4X:
    jo = Me 
    Dim privatekey As String = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCbQxh5MndksiaO" _
& "KZjLs/Ps9DJYNQlwvQA2Qa7gf1AP4aQRdNQR1tThTH2vc2+rdveZ9rWnS+8gb3AC" _
& "AvHk5t2NQF347v3k5Lh+b/t/AQtEsUzp7kspna8vUNHZnPJpcvpWocodQcZZu9B8" _
& "iTdOiJjJxLgivTn87p9LIA4uOr15sm6hT5CB8o+rm+Q2EB0lAIUFhqcFKe/MMf0X" _
& "CyhLlGtjo/nNDf+f9bLHu0wVqmEFS5QFin3+ANvRJcYnQDxkfLWTP8Xa+ryq7xPh" _
& "2TDWf5X3keWE8/8yeEfi/fzx8pSz9zVr5VhAZ42MFE5Z+rHb3ISx0hcpZ9B3iXj1" _
& "UTJmjVkjAgMBAAECggEAPTpB6fWNYM/iZXig8V/nRx/rxJ4xuVxHO54+bPi35XNp" _
& "pJqqUNij/a8Q94Ix3O/e58ADVdSbmfJoPNrVcpvabewewt7YkA3kdbQKfUS3oWC8" _
& "AJqA2In+/k1EXClI7W2yrDxEPLJmGpf2uU3RWi6C9jqWFNkDh6vAdLtQ7Eks1FL4" _
& "tao4cdWAz23U9VqoYy1SSlUg1UVrLF0jePyp1R4YEFeqfDwHAvY6CpDocWdOAW66" _
& "YnASq5Z0wLy1jrkE8BrpMdMvss1FxgkmzJMkr53vrQ6XRTE2QqODevhvQdrCEejd" _
& "kGLkW3Cy3+rIOpqaeu5Se/dQ7nmSpYDd/LWjVKI3uQKBgQD4q4+rWyHTNQO4yl+w" _
& "sEWTLTS0JodY4BF6JWChl4Frku1KsXNOqgE2BGSiv29U6WJcDE1luwh9wU2T9/1n" _
& "4mlMy1hJ8qgUzVhIIgKHAr7FealnO7Zt/PV9c4LoEHRl/BaitLEenCbj9n+aXwsj" _
& "GmngkbmvW5Jcp1ykNbu9qQIR2QKBgQCf1q/ctjEI9pr6mZ6MBq8nFHx9oWHyjexw" _
& "wac/0U+LUY0CPqtTLnkNHc4UsYGk2lM31VExFn+oKs1rxndWRwgAEY/bDxFwFxcy" _
& "O7nTgak3hJYzc/Zv4kDatIrDtpFV18nBnDjeTj0QTBJFtaOoHjqJrNQGTCHrvK+l" _
& "kXaKdxdpWwKBgQCrwzUVk3klvjS363F1RgyIwGzrEsHibcMkr7SzaUcH2xD0yuVu" _
& "rJbxjM9Gaxyndh1un0DGyA3xbxf64Qy0OPurA7oUOfxHgh88k+FTCF5lYMfWerRj" _
& "/JpE8Qi26sa7uwiXkl/VWN60D4vMQWIb+R6w83di3MmYHjIrasInGxpG4QKBgQCF" _
& "9tnOkqTmbknWX4qSscd7aaAta2U8ddcFaklTI4sXqXIVv5C4Vur+I0zl6yBNmu7E" _
& "jPVPvxufRsCE5AKBPWdnJ1D6uNZUrAW1BHnq23GIJof89+REQc05gLgM8Kc+220t" _
& "6FGBgPGNBzUJWAOilDNb0I6j+Is1mR9eFVzVEJt+oQKBgQC8Fs7az7mhv00ffxAD" _
& "ehc2w9KS7vrF9FVSSiFzv+QtaHCHnGrQMepAomk8DHfT4g+FJIJyZLs89qFugTYR" _
& "aozcVkxASaPkx8TljYLRs7oKu68+JyCLxlAJRuw8qO9PV0fYiqurOWBdspVmjcBl" _
& "4vP0nmpHoNfeaJMY8PF/cB2hzA=="

    jo.RunMethod("getSignedText", Array("SOME_NORMALIZED_TEXT",Null,privatekey))

    ' in case you get an "algorithn not found error", this call below will tell
    ' you what's available on your computer.
    ' jo.RunMethod("whatsAvailable",Null)

then:
B4X:
#if Java
import java.security.Signature;
import java.util.Base64;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.util.Arrays;
import java.security.Provider.Service;

public static String getSignedText(String text, String algorithm, String privateKey) {
    try {
  
        // convert privateKey string to PrivateKey.class
      
        byte[] clear = Base64.getDecoder().decode(privateKey.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clear);
        KeyFactory fact = KeyFactory.getInstance("RSA");
        PrivateKey priv = fact.generatePrivate(keySpec);
        Arrays.fill(clear, (byte) 0);
      
      
        byte[] data = text.getBytes("UTF8");
           Signature sig = Signature.getInstance(algorithm == null ? "SHA256withRSA" : algorithm);
           sig.initSign(priv);
           sig.update(data);
           byte[] signatureBytes = sig.sign();
           String retString = Base64.getEncoder().encodeToString(signatureBytes);
           BA.Log(retString);
           return(retString);
    }
    catch(Exception e) {
        BA.Log(e.toString());
        return(e.toString());
    }
}

    import java.util.TreeSet;
    import java.security.Security;
    import java.security.Provider;
      
    public static void whatsAvailable() {
        TreeSet<String> algorithms = new TreeSet<>();
        for (Provider provider : Security.getProviders())
            for (Service service : provider.getServices())
                if (service.getType().equals("Signature"))
                    algorithms.add(service.getAlgorithm());

        for (String algorithm : algorithms)
            BA.Log(algorithm);
    }
#End If

notes:
1) this line of java code you originally posted is wrong:

there is no space before SHA256WITHRSA (" SHA256WITHRSA"), and "SHA256WITHRSA" itself is SHA256withRSA

2) your private key is a string which has to be converted to an instance of PrivateKey before it can be signed
3) your private key does includes the header and trailer. they have to be stripped before using the key for signing. i did that in the b4j code. but it could be done in the inline java.
4) the result is "similar" to what you're looking for. where what you're expecting has "#"'s, my version has "+". i don't know why the difference, but it is simple enough to subsititue "#" for "+" in the result (assuming the rest was what you're expecting).
when i ran the code with a random string, i got: L+0Kka+ALrt8ues2lAx2Ad+MVjbGfRJ8cOKtPTM5sC46wG1aUbWkMQHUDZbT9bwYGF+isemhncbkcL+SS4GKuPyVOX5AR7rfX44br+GP/eZFzQwFvwJDtjoCY5vm6cpKyLwbKiynNUttEoqYQGf1qfhJXFtxH2As1F4rcRbfM/pFmJtELgGTKotYhjZZnZQ7lPPOT/URGn3VUBuOUzFl9laiJbiYow/TvzZT1iK/f2mX94yd9yPczSI24alSHLkwJgfhiBqSON+9XPFnUH+BNsbK/YrDIp6BCW+lIFwTPUhYJTcuaghQqLm9hJQZ/vSUHeu6RP2PYFSWIU6R3eSzgg==

also, your expected output is a hex string. what i got using the code snippet you posted is base64. i don't know if there is a missing step in the snippet. since there was already an error in one line, i suppose it's possible.
5) not all algorithms are supported on all devices and in all versions of java. in such occasions, you get an exception, eg, "algorithm not found..." before i noticed the error in the snippet, i got that exception. in order to know which algorithms a device supports, i added another inline routine. if you run it, it spits out a list of all the algorithms supported on your device. when i ran it, i got a list:

MD2withRSA
MD5andSHA1withRSA
MD5withRSA
NONEwithDSA
NONEwithECDSA
NONEwithRSA
SHA1withDSA
SHA1withECDSA
SHA1withRSA
SHA224withDSA
SHA224withECDSA
SHA224withRSA
SHA256withDSA
SHA256withECDSA
SHA256withRSA
SHA384withECDSA
SHA384withRSA
SHA512withECDSA
SHA512withRSA

when i saw "SHA256withRSA", i realized there was a misspelling in the java snippet.

anyway, plug the 2 snippets posted above into your b4j code and see what you get when you use your private key

thank you
I think the code is correct
I connected two pieces of code together and I get an output
I tested and my output is the same as yours
But after sending the request, the response of the server is that the signature of the package to be sent is not correct.
=========================================================
B4X:
    Dim k As KeyPairGenerator
    K.Initialize("RSA", 2048)
    Dim PrivateKeyS As String
    File.ReadString(File.DirApp, "Private.txt").Trim
    PrivateKeyS=PrivateKeyS.Replace("-----BEGIN PRIVATE KEY-----","")
    PrivateKeyS=PrivateKeyS.Replace("-----END PRIVATE KEY-----","")
    PrivateKeyS=PrivateKeyS.Replace(CRLF,"")
    
    Dim su As StringUtils
    Dim PrivateKeyB(0) As Byte = su.DecodeBase64(PrivateKeyS)
    k.PrivateKeyFromBytes(PrivateKeyB)
    
   ''k.PrivateKey

I even used the code below and sent the private key directly to the function, but the server's response is the same
Probably the problem is from somewhere else. I have to check and try and make mistakes
Thank you all
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
i've looked up several signing methods. they follow the same steps as the snippet you posted.

you understand that the result is a byte array. for "convenience" that array is normally converted to a
base64 string. it looks like your server is expecting a hex string. if i leave off the last step (converting the
byte array to base64) and simply return the bytes, b4j can use byteconverter to convert the bytes to a hex
string. but even then, it still doesn't look exactly like what you say you're expecting (there are no "#" characters
in the hex string).

since the examples i found are the same as what i implemented, my guess would be an issue with the
code that converts your private key to a PrivateKey object. there seem to be more than 1 way to do that. does
your server have any documentation? can you share the url?
 
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
i've looked up several signing methods. they follow the same steps as the snippet you posted.

you understand that the result is a byte array. for "convenience" that array is normally converted to a
base64 string. it looks like your server is expecting a hex string. if i leave off the last step (converting the
byte array to base64) and simply return the bytes, b4j can use byteconverter to convert the bytes to a hex
string. but even then, it still doesn't look exactly like what you say you're expecting (there are no "#" characters
in the hex string).

since the examples i found are the same as what i implemented, my guess would be an issue with the
code that converts your private key to a PrivateKey object. there seem to be more than 1 way to do that. does
your server have any documentation? can you share the url?

The instructions are in persian language, I think it will not help you
But it has an SDK + sample that I will upload for you
These files are provided by the company
I have uploaded and configured the public key file on the server
Ready to send request
I put the necessary information in the file


The steps are as follows
1.Get information from the server
2.Receive jwt token from server(Requires signature and encryption)
3.Send invoice(Requires signature and encryption )
Invoice tracking(Requires signature and encryption)
 
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
2.Receive jwt token from server(Requires signature and encryption)

wait a minute; didn't we just go through this on a different thread? i've downloaded the .zip, but i have to tell you, if something doesn't jump right out, i can't promise to get involved in some long term debugging project. i will take a look.

update: i took a look. the first thing that jumps out is the cookie. how are you handling that? that could easily be why the request returns an error. my guess is the error message is not terribly helpful, but you should post what it says anyway.

the second thing that jumps out is the size of the sdk. i think converting it to b4x would be a big project. the requests to the server are basic okhttp3 requests, the same ones used by our okhttputils2. they aren't the real problem. it's all the security protocols that require some specific knowledge.

the alternative is to run it in inline java, which - technically - may not be such a big project. it depends on how much of it runs by itself. that is, you plug in a couple parameters, and the code just runs. but you're still left with the various encryption protocols.

if this is a hobby for you, i wish you the best. if this is your job, then i think you should pay someone to adapt the sdk. there are forum members who could handle it, but it's going to take some concentration. i mean, i'm curious, but not that curious. i don't think i can do much more on my end. solving a particular conversion method is one thing; i think this is a much larger project.
 
Last edited:
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
wait a minute; didn't we just go through this on a different thread? i've downloaded the .zip, but i have to tell you, if something doesn't jump right out, i can't promise to get involved in some long term debugging project. i will take a look.

update: i took a look. the first thing that jumps out is the cookie. how are you handling that? that could easily be why the request returns an error. my guess is the error message is not terribly helpful, but you should post what it says anyway.

the second thing that jumps out is the size of the sdk. i think converting it to b4x would be a big project. the requests to the server are basic okhttp3 requests, the same ones used by our okhttputils2. they aren't the real problem. it's all the security protocols that require some specific knowledge.

the alternative is to run it in inline java, which - technically - may not be such a big project. it depends on how much of it runs by itself. that is, you plug in a couple parameters, and the code just runs. but you're still left with the various encryption protocols.

if this is a hobby for you, i wish you the best. if this is your job, then i think you should pay someone to adapt the sdk. there are forum members who could handle it, but it's going to take some concentration. i mean, i'm curious, but not that curious. i don't think i can do much more on my end. solving a particular conversion method is one thing; i think this is a much larger project.
This system has two versions
This thread is v1
That thread was v2

The main problem is the signature step
Now the normalization and signing of the code works well, but the server gives a negative answer, probably I missed something in the middle
Maybe there is a step between these, that's I think it is possible to find it inside the SDK and copy that piece of code or convert the mechanism to b4x.

If it is possible, please check once more and tell the work steps to get token according to the source
This could be a clue and I will delve further into it

Now, almost the only problem is generating the signature parameter(for gettoken request), which is one of the body parameters, and the server gets an error about this

According to the instructions, it is said that
First, merge Json Body and Header
Then normalize json>string
Then sign string with the private key
Then put it as parameter in body
Then send http post request
I also send with b4j, everything is correct except for the signature parameter
Because the server replies that the signature is not correct
 
Last edited:
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Can you post the error message the server replies with.
 
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
Can you post the error message the server replies with.
j.Response.StatusCode = 401
j.Response.ErrorResponse = {"timestamp":1711873157217,"requestTraceId":"c465f019ab91e5c30c2bdff28504a2c9","errors":[{"code":"4011","message":"The signature of the sent package is not correct"}]}

b4j sample attached
download jars : https://www.dropbox.com/scl/fi/rfyvv6ahn8a8g9hplrspw/jars.zip?rlkey=d594utbloquomqrpumtoqrjxh&dl=0

get server information return org_public_key and keyid
Maybe it uses this data in the string signature, but it didn't say anything in the instructions, maybe I don't understand
----------------------------------------------------------------------
this is another sample with php from githube

golang sdk

paython
 

Attachments

  • b4jv1.zip
    8 KB · Views: 42
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
for what it's worth, i put your private key on https://8gwifi.org/rsasignverifyfunctions.jsp#google_vignette to generate a signature. it is exactly the same as the signature i generate with the code that i posted.
thanks
Did you set the key size to 2048??
The only place I can check is the parameters...
I think that the Jason sent to the server and the Jason sent to the normalizer are not the same. It is my mistake. As a result, the signature generated on the client side is not verified on the server side.
 
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
thanks
Did you set the key size to 2048??
The only place I can check is the parameters...
I think that the Jason sent to the server and the Jason sent to the normalizer are not the same. It is my mistake. As a result, the signature generated on the client side is not verified on the server side.
yes.
i agree: next step the json. b4x has "json.tostring". i think "normalizing" is just a fancy way to do "json.tostring". skip the normalizing step and try passing json.tostring
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
i ran b4jv1. get_token never returns... it's unclear exactly what was being logged, so i put labels on the log entries so i could see where i was in the app.

B4X:
in get_token

before normalizing: {
 "packet": {
  "packetType": "GET_TOKEN",
  "uid": "",
  "encryptionKeyId": null,
  "data": {
   "username": "A2HGE9"
  },
  "dataSignature": null,
  "iv": null,
  "fiscalId": "A2HGE9",
  "retry": false,
  "symmetricKey": null,
  "signatureKeyId": null
 },
 "requestTraceId": 1711919894952,
 "timestamp": 1711919894952
}

signature: aBgLgU1nQUkFK6pWRiZ8PLxmu0KhQZo/9HUYlC2On6zusP1YPBFhuBd1Z+lm0jPvvpAudYhO78Tn98kPWzwZ4Qv9ar9/0n7qq46CJdABOyMuBqwUZWtvaXIskAZt/28nSfyUuOV2ZKfdPzX5ENzn7Z6aSwEqT5bJTLTld4rYgkeF+IrHPk6dGuWhXRV7e0jCDWYAT8aGd6O0JZruFzp9QpE2ZXgqYhIlc9ivECA+S0Y/ScGAF2Z3T5fE2/nSeJ7yl5xppMMHKsc13M5gl7SGHENE6YqgeoeYuRleTqdUoYLGvQAg9tlTihLNjRyPn7uAvl3bMVQtFubZ0lTPKuK8aw==

sending request to https://sandboxrc.tax.gov.ir/req/api/self-tsp/sync/GET_TOKEN

that's the end of the log entry. no "success", no "error". nothing.

wait, maybe server doesn't like where i am. i will try again with my vpn as close as i can get to iran.
 

Attachments

  • cap.png
    cap.png
    59.5 KB · Views: 33
Last edited:
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
i ran b4jv1. get_token never returns... it's unclear exactly what was being logged, so i put labels on the log entries so i could see where i was in the app.

B4X:
in get_token

before normalizing: {
 "packet": {
  "packetType": "GET_TOKEN",
  "uid": "",
  "encryptionKeyId": null,
  "data": {
   "username": "A2HGE9"
  },
  "dataSignature": null,
  "iv": null,
  "fiscalId": "A2HGE9",
  "retry": false,
  "symmetricKey": null,
  "signatureKeyId": null
 },
 "requestTraceId": 1711919894952,
 "timestamp": 1711919894952
}

signature: aBgLgU1nQUkFK6pWRiZ8PLxmu0KhQZo/9HUYlC2On6zusP1YPBFhuBd1Z+lm0jPvvpAudYhO78Tn98kPWzwZ4Qv9ar9/0n7qq46CJdABOyMuBqwUZWtvaXIskAZt/28nSfyUuOV2ZKfdPzX5ENzn7Z6aSwEqT5bJTLTld4rYgkeF+IrHPk6dGuWhXRV7e0jCDWYAT8aGd6O0JZruFzp9QpE2ZXgqYhIlc9ivECA+S0Y/ScGAF2Z3T5fE2/nSeJ7yl5xppMMHKsc13M5gl7SGHENE6YqgeoeYuRleTqdUoYLGvQAg9tlTihLNjRyPn7uAvl3bMVQtFubZ0lTPKuK8aw==

sending request to https://sandboxrc.tax.gov.ir/req/api/self-tsp/sync/GET_TOKEN

that's the end of the log entry. no "success", no "error". nothing.

wait, maybe server doesn't like where i am. i will try again with my vpn as close as i can get to iran.

I tested with American IP and did not see any problem
It is not limited

I found another software that implements this system
I tested with my private key and ID, it returned the token without any problem
I put the information here, maybe it will help

B4X:
curl --location 'https://sandboxrc.tax.gov.ir/req/api/self-tsp/sync/GET_TOKEN' \
--header 'accept: application/json' \
--header 'requesttraceid: 66568c81-4f23-4731-ac9e-a337bf03f0fe' \
--header 'timestamp: 1711923522879' \
--header 'content-type: application/json; charset=utf-8' \
--header 'host: sandboxrc.tax.gov.ir' \
--header 'cookie: cookiesession1=678B28C579F7F565C921D1B41C634392' \
--header 'content-length: 642' \
--header 'expect: 100-continue' \
--header 'x-postman-captr: 3593779' \
--data '{"packet":{"uid":"eb10ad53-3d16-41de-bbc1-16ce8e8eba8f","packetType":"GET_TOKEN","retry":false,"data":{"username":"A2HGE9"},"encryptionKeyId":null,"symmetricKey":null,"iv":null,"fiscalId":"A2HGE9","dataSignature":null,"signatureKeyId":null},"signature":"mJTA/lLvMAHCtr4uCZk9N4BFtBK9utb2gLrwMxbvTMHX/OGNsrX0bx7vUX1slR1ZGWoDzdPHbqr+RD81ya6XKCDJwwEiCiBrNBAWftAH3AVyR4DE8raD3EquBCfFTWtUBfQHyFKaYb9b7o+DcZ1Nfm3fp2Bnmq1i1QQFtd4PXjs12GJ2h6UZefgD2nPYlPrpPCUe6YFlZlcTQa907ZxErs7/wramZ6DRNs3Wss1UyqZGt0wneL3C+arxQqIrkW5JdB1vj1pt9J31GJ9ko2GoSdDHjQrp76xbQoMzqCH+qYqoL1dUkuIF/Z2gmBEdeRU3Igt9dwFTj79CzqOY2jaa3g==","signatureKeyId":null}'

response:
{
    "signature": null,
    "signatureKeyId": null,
    "timestamp": 1711923523506,
    "result": {
        "uid": "eb10ad53-3d16-41de-bbc1-16ce8e8eba8f",
        "packetType": "TOKEN_RESULT",
        "data": {
            "token": "eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJBMkhHRTkiLCJ0b2tlbklkIjoiYWE1YzVjYmEtYThmYi00ODhiLWE1NTYtMGUyZWI1MmQ0ZTNkIiwiY3JlYXRlRGF0ZSI6MTcxMTkyMzUyMzUwNSwiY2xpZW50VHlwZSI6Ik1FTU9SWSIsInRheHBheWVySWQiOiIxNTgzMjg1NDIzIiwic3ViIjoiQTJIR0U5IiwiZXhwIjoxNzExOTM3OTIzLCJpc3MiOiJUQVggT3JnYW5pemF0aW9uIn0.ol3wZFdP6Ql6FzNOqG7ZjeK3a5eT9UawgQJeArd9ljhHnYRkpLxXK_79dV7kmP0tTwJinWr_Hauw4mJkRMQT2g",
            "expiresIn": 1711937923505
        },
        "encryptionKeyId": null,
        "symmetricKey": null,
        "iv": null
    }
}


and paython sample for get_token
https://github.com/mohammadreza1408/pos_pyhon/blob/main/get_token.py
 
Last edited:
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
@drgottjr

I finally found the problem
Apparently, there is a problem in merging two maps

The normalized string should look like this (this is Currect)
I converted and applied manually and the result was successful and the token was received from the server
A2HGE9#########GET_TOKEN#1711940931865#false###1711940931865##

But my normalizing function returns something like this (this is wrong)
A2HGE9#########GET_TOKEN#false#####1711940931865#1711940931865
-------------------------------------------------------------------------------
I think there is a problem with my merge method that returns an incorrect string then incorrect signature

Do you have any idea how to get this correct format??
 
Upvote 0

behnam_tr

Active Member
Licensed User
Longtime User
Solved.

some bug fixed

1:
    Dim normalmap As Map
    normalmap.Initialize
    normalmap.Put("requestTraceId",time)
    normalmap.Put("timestamp",time)
    normalmap.Put("uid",Null)
    normalmap.Put("packetType", "GET_TOKEN")
    normalmap.Put("retry", False)
    normalmap.Put("data", CreateMap("username":clientid))
    normalmap.Put("encryptionKeyId", Null)
    normalmap.Put("symmetricKey", Null)
    normalmap.Put("iv", Null)
    normalmap.Put("fiscalId","")
    normalmap.Put("dataSignature",Null)
   
   ' normalize(normalmap)

2:
// adding java code to normalize sub
//sort map by key

Map<String, Object> map = new TreeMap<>(headerMap);

Thank you friends for your kindness
@drgottjr
@Daestrum
 
Upvote 0
Top