ibmi-brunch-learn

Announcement

Collapse
No announcement yet.

Issues with Prototype of Java Method - Base64.getDecoder

Collapse
This topic is closed.
X
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Issues with Prototype of Java Method - Base64.getDecoder

    Greetings all. I am having issues getting my prototype correct for a couple Java methods. I tried to find examples of native Base64 encoding/decoding but found different versions where some used Scott Klement's open source Base64 utility, others use a set of APIs that I can't remember what they are, and still others user the apr_base64 APIs. None of which I can seem to get working which I'm sure is due to my inability to piece things together.

    I decided not to reinvent the wheel as I know Java can do it. This is the code from the Java example I am attempting to recreate in RPG:
    Code:
     SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(merchantSecretKey), "HmacSHA256");
    I'm not sure I can do the above with APIs on the IBM i so I figured I would prototype the various methods in RPG and try it from there. I get an error in my RPG program when it gets to a certain point when working with the java.util.Base64 class and the getDecoder method. When I look at my joblog, this is what it says: Java exception "java.lang.NoSuchMethodError: java/util/Base64.getDecoder()Ljava/util/Base64/Decoder;" when calling method "getDecoder" with signature "()Ljava.util.Base64.Decoder;" in class "java.util.Base64".

    Here is a link to the Java Docs regarding the Base64 class: https://docs.oracle.com/javase/8/doc...il/Base64.html.

    I also ran javap -s java.util.Base64 on the IBM i I'm using and it looks like the signature matches my prototype:
    Compiled from "Base64.java"
    public class java.util.Base64 {
    public static java.util.Base64$Encoder getEncoder();
    descriptor: ()Ljava/util/Base64$Encoder;

    public static java.util.Base64$Encoder getUrlEncoder();
    descriptor: ()Ljava/util/Base64$Encoder;

    public static java.util.Base64$Encoder getMimeEncoder();
    descriptor: ()Ljava/util/Base64$Encoder;

    public static java.util.Base64$Encoder getMimeEncoder(int, byte[]);
    descriptor: (I[B)Ljava/util/Base64$Encoder;

    public static java.util.Base64$Decoder getDecoder();
    descriptor: ()Ljava/util/Base64$Decoder;

    public static java.util.Base64$Decoder getUrlDecoder();
    descriptor: ()Ljava/util/Base64$Decoder;

    public static java.util.Base64$Decoder getMimeDecoder();
    descriptor: ()Ljava/util/Base64$Decoder;
    }


    Below is my code. Realize that this is just a test program so I've tried a few other things and commented them out while I work on other pieces. Any help with what I am missing would be greatly appreciated and/or pointing me to resources that show how to use available APIs on the IBM i to do what I want.
    Code:
         H DFTACTGRP(*NO) ACTGRP(*CALLER)
         H THREAD(*SERIALIZE)
          // /DEFINE JNI_COPY_ALL
          // /copy QSYSINC/QRPGLESRC,JNI
          // /UNDEFINE JNI_COPY_ALL
    
          /DEFINE OS400_JVM_12
          /copy qsysinc/qrpglesrc,jni
          /undefine OS400_JVM_12
    
          /DEFINE JAVA_UTIL_COPIED
          /copy allsrc,javautil
          /UNDEFINE JAVA_UTIL_COPIED
    
    
         D newString       PR              O   EXTPROC(*JAVA
         D                                     : STRING_CLASS
         D                                     : *CONSTRUCTOR)
         D    value                   65535A   VARYING CONST
    
          * Define a prototype to retrieve the value of a String
         D getBytes        PR         65535A    VARYING
         D                                      EXTPROC(*JAVA:
         D                                       STRING_CLASS:
         D                                       'getBytes')
    
           * Define the getDecoder Method
         D getBase64Decoder...
         D                 PR              o   extproc(*JAVA:
         D                                     BASE64_CLASS:
         D                                     'getDecoder')
         D                                     CLASS(*JAVA:
         D                                     'java.util.Base64.Decoder')
         D                                     STATIC
    
          * Define a prototype to decode a byte array
         D base64Decode    PR              o      
         D                                     extproc(*JAVA
         D                                     : BASE64_DECODER_CLASS
         D                                     : 'decode')
         D                                     CLASS(*JAVA              
         D                                     : 'java.lang.Object')
         D byteArray                       o   CLASS(*JAVA
         D                                     : 'java.lang.Object')
    
    
         D STRING_CLASS    C                   'java.lang.String'
         D BASE64_DECODER_CLASS...
         D                 C                   'java.util.Base64.Decoder'
         D BASE64_CLASS    C                   'java.util.Base64'
         D SHA256          C                   'HmacSHA256'
    
         D HTTP_METHOD_POST...
         D                 C                   'POST'
    
         D base64Decoder1  s               o   Class(*JAVA: BASE64_DECODER_CLASS)
         D base64Decoder2  s               o   Class(*JAVA: BASE64_DECODER_CLASS)
         D byteArray1      s               o   Class(*JAVA: 'java.lang.Object')
         D httpSignature1  s               o   Class(*JAVA: STRING_CLASS)
    
         D tempField       s           1000A   inz
         D env             s               *   inz(*null)
    
          /free
    
            env = getJniEnv();
            beginObjGroup(env);          
            base64Decoder1 = getBase64Decoder();
            endObjGroup(env);
            *inlr = *on;
    
          /end-free
    
          *----------------------------------------------------------------      
          * beginObjGroup - start a new group of objects that can all      
          *                 be deleted together later      
          *----------------------------------------------------------------
         P beginObjGroup   b                   export    
         D beginObjGroup   pi            10i 0    
         D   env                           *   const    
         D   capacityParm                10i 0 value options(*nopass)    
         D rc              s             10i 0    
         D capacity        s             10i 0 inz(100)      
          /free          
                 JNIENV_p = env;                      
                 if (%parms >= 2);              
                     capacity = capacityParm;          
                 endif;                      
                 rc = PushLocalFrame (JNIENV_p : capacity);          
                 if (rc <> 0);              
                     return JNI_GROUP_NOT_ADDED;          
                 endif;          
                 return JNI_GROUP_ADDED;      
          /end-free
         P beginObjGroup   e          
          *----------------------------------------------------------------      
          * endObjGroup - end the group of objects that was started      
          *               most recently      
          *----------------------------------------------------------------    
         P endObjGroup     b                   export    
         D endObjGroup     pi            10i 0    
         D   env                           *   const    
         D   refObjectP                    o   class(*java:'java.lang.Object')    
         D                                     const    
         D                                     options(*nopass)    
         D   newObjectP                    o   class(*java:'java.lang.Object')    
         D                                     options(*nopass)    
    
         D retVal          s               o   class(*java:'java.lang.Object')    
         D refObject       s                   like(refObjectP) inz(*null)    
         D newObject       s                   like(newObjectP)    
          /free          
                 JNIENV_p = env;                      
                 if %parms() >= 2;              
                     refObject = refObjectP;          
                 endif;                      
                 newObject = PopLocalFrame (JNIENV_p : refObject);                      
                 if %parms() >= 3;              
                      newObjectP = newObject;          
                 endif;                      
                 return JNI_GROUP_ENDED;      
          /end-free    
         P endObjGroup     e
    
         P freeLocalRef    b
    
         D freeLocalref    pi
         D env                             *   value    
         D localRef                        o   class(*java
         D                                         : 'java.lang.Object')
         D                                     value
    
          /free
             jniEnv_P = env;
             DeleteLocalRef(env: localRef);
          /end-free
         P freeLocalRef    e
    
          *----------------------------------------------------------------
          * getJniEnv - get the JNI environment pointer
          * Note: This procedure will cause the JVM to be created if
          *       it was not already created.
          *----------------------------------------------------------------
         P getJniEnv       b                   export
         D getJniEnv       pi              *
    
         D attachArgs      ds                  likeds(JavaVMAttachArgs)
         D env             s               *   inz(*null)
         D jvm             s                   like(JavaVM_p) dim(1)
         D nVms            s                   like(jsize)
         D rc              s             10i 0
         D obj             s               o   class(*java
         D                                         : 'java.lang.Integer')
         D newInteger      pr              o   extproc(*java
         D                                           : 'java.lang.Integer'
         D                                           : *constructor)
         D   value                       10i 0 value
          /free
             monitor;
                // Get the current JVM
                rc = JNI_GetCreatedJavaVMs(jvm : 1 : nVms);
                if (rc <> 0);
                   // Some error occurred
                   return *null;
                endif;  
                if (nVms = 0);
                   // The JVM is not created yet.  Call a Java
                   // method to get the RPG runtime to start the JVM
                   obj = newInteger(5);
    
                   // Try again to get the current JVM
                   rc = JNI_GetCreatedJavaVMs(jvm : 1 : nVms);
                   if (rc <> 0
                   or  nVms = 0);
                      // Some error occurred
                      return *null;
                   endif;
                endif;
                // Attach to the JVM
                JavaVM_P = jvm(1);
                attachArgs = *allx'00';
                attachArgs.version = JNI_VERSION_1_2;
                rc = AttachCurrentThread (jvm(1) : env
                                        : %addr(attachArgs));
                if (rc <> 0);
                   return *null;
                endif;
    
                // Free the object if we created it above while
                // getting the RPG runtime to start the JVM
                if obj <> *null;
                   freeLocalRef (env : obj);
                endif;
             on-error;
                return *null;
             endmon;
             return env;
          /end-free
         P getJniEnv       e
    Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov

  • #2
    Your code is looking for:
    Code:
    [B]java.util.Base64.getDecoder
    [/B]


    The javap output shows:

    Code:
    java.util.Base64$Decoder getDecoder()
    These look different to me?!

    Comment


    • #3
      Originally posted by Scott Klement View Post

      These look different to me?!
      Thanks for the response and the help. Due to lack of experience with prototyping Java methods in an RPGLE program, I made an incorrect assumption that the Base64$Decoder output from javap was just an IBM i representation. Kind of like where it has
      Code:
      Ljava/util/Base64$Decoder
      It doesn't have periods between java and util like it does for the class so I just chalked it up to an IBM i thing. However, when I changed my BASE64_DECODER_CLASS constant to be java.util.Base64$Decoder, it did not throw a Java exception. I haven't tried any more as I want to leave it on good terms so I don't throw my computer out of frustration like I wanted to when I made my initial post.
      Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov

      Comment


      • #4
        I'm no Java expert (in fact, I hate Java and avoid it as much as possible. There was a time when this was difficult, but today it is not, there's almost never a reason to use Java anymore... but I digress.) but I think the $ refers to a private member that is exported for the use of other classes within the same parent class (or maybe same package?). Something like that.

        I'm not familiar with the Base64 tools that are provided within Java. I haven't tried these particular methods.

        If you want help with my RPG encoder/decoder for BASE64, I'd be glad to help with that.

        You might also consider using the SQL support for base64 which is really easy to use.

        Comment


        • #5
          Originally posted by Scott Klement View Post
          I'm no Java expert (in fact, I hate Java and avoid it as much as possible. There was a time when this was difficult, but today it is not, there's almost never a reason to use Java anymore... but I digress.) but I think the $ refers to a private member that is exported for the use of other classes within the same parent class (or maybe same package?). Something like that.

          I'm not familiar with the Base64 tools that are provided within Java. I haven't tried these particular methods.

          If you want help with my RPG encoder/decoder for BASE64, I'd be glad to help with that.

          You might also consider using the SQL support for base64 which is really easy to use.
          Thanks for the offer to help. I know that you are a busy person so I really appreciate it. Right now I am just in my discovery phase so Java was a way for me to see results on the IBM i as well as on my PC running the same code. It's just the way I'm wired I guess.

          Regarding your BASE64 encoder/decoder, is that something that is also included in HTTP_API or do I need to download the separate Base64 tool from your website? We already have HTTP_API installed on our IBM i. A bit thank you for that tool as well. Any link you can provide or example code for decoding/encoding Base64 using your tool would be helpful. Truth be told, your open source projects are usually my first choice but I think I had problems finding an example while doing an Internet search so that's the other reason I chose Java. If there are examples included in the HTTP_API source or in the Base64 tool SAVF, please let me know.

          I actually forgot about the SQL BASE64ENCODE/BASE64DECODE scalar functions so thanks for the reminder.

          Also, any suggestions for handling the SHA256 decryption of the merchantSecretKey value would also be appreciated.
          Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov

          Comment


          • #6
            It is a separate tool from HTTPAPI, but also open source. You can download it from here:


            To find an example, I did a quick Google search for "RPG base64_encode" and found this:
            Re: Base64 encoding/decoding -- Interesting. V= is invalid. V is #21, which is not legal. When the string ends with a =, the last two bits of the 3rd byte must be zero, which means the number has to be a multiple of 4. Since 21 isn't a multiple of 4, something is wrong. I'm trying to re...


            I'm sure you can find more if you look around with Google. Or, feel free to ask questions here.

            Comment


            • #7
              Never heard of SHA256 as something that can be decrypted. It is normally a hash algorithm, so you can use it to create a unique hash for a string/document, but it cannot be reversed back to the hash.

              Comment


              • #8
                Originally posted by Scott Klement View Post
                It is a separate tool from HTTPAPI, but also open source. You can download it from here:


                To find an example, I did a quick Google search for "RPG base64_encode" and found this:
                Re: Base64 encoding/decoding -- Interesting. V= is invalid. V is #21, which is not legal. When the string ends with a =, the last two bits of the 3rd byte must be zero, which means the number has to be a multiple of 4. Since 21 isn't a multiple of 4, something is wrong. I'm trying to re...


                I'm sure you can find more if you look around with Google. Or, feel free to ask questions here.
                Thank you for the reply. I did a Google search a while ago but maybe my memory failed me and I should have double checked before making the statement about having problems finding an example. My apologies for making you do the work for me. That's not usually who I am. I will review the midrange.com info and see if I have any issues that require more questions. Thanks again for the help.
                Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov

                Comment


                • #9
                  Originally posted by Scott Klement View Post
                  Never heard of SHA256 as something that can be decrypted. It is normally a hash algorithm, so you can use it to create a unique hash for a string/document, but it cannot be reversed back to the hash.
                  I realize you said you were not a Java expert but here is some more of the Java code for the example that the vendor provided:
                  Code:
                   
                   /* Signature string generated from above parameters is Signed with SecretKey hashed with SHA256 and base64 encoded.  *  Secret Key is Base64 decoded before signing */ SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(merchantSecretKey), "HmacSHA256"); Mac aKeyId = Mac.getInstance("HmacSHA256"); aKeyId.init(secretKey); aKeyId.update(signatureStr.getBytes()); byte[] aHeaders = aKeyId.doFinal();  String base64EncodedSignature = Base64.getEncoder().encodeToString(aHeaders);  return base64EncodedSignature;
                  I will admit that I don't fully understand all the above but from what I can tell, it decoding a key that is hashed with SHA256 and BASE64 encoded. It then appears to take that decoded valued and adds the signature generated earlier in the method which it then encodes using BASE64. The comment is from whoever wrote the sample. My guess is that when it BASE64 encodes again with the addition of the signature, it can't BASE64 encode something that is already BASE64 encoded and come up with aproperly encoded result. When I said decrypted I may have misspoke as after reviewing it again, it may just be decoding it rather than decrypting it. However, it is still specifying the hash method of SHA256. I am just not sure how to accomplish the same thing. I will have to dig into those classes a bit more and try to get a handle on exactly what it's doing. For now, I would say that we can consider this topic answered and I will post more questions if needed.
                  Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov

                  Comment


                  • #10
                    This code doesn't decrypt the secret key, it just decodes it from base64, then uses it to update the signature. Its also calculating an HMAC rather than just a straight SHA-256 hash, which you didn't mention previously (unless I missed it.)

                    Here's the API for calculating an HMAC:


                    If you look at the API you'll see that there's parameters for the input data and the key. It looks to me (albeit, I'm no Java expert) that it is using the base64-decoded merchant key as the key parameter, and the bytes that make up the signature as the data. It is then base64-encoding the output.

                    Comment

                    Working...
                    X