StringUtil.convertEncodedString returns System Error: Failed invoking <symbol>

I'm trying to use the Cryptography.Cipher class to decrypt a text I previously encrypted using the following configs:



This is the code I have atm:

hidden function convertStringToByteArray(plainText as String) as ByteArray  {
    var options = {
        :fromRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
        :toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
        :encoding => StringUtil.CHAR_ENCODING_UTF8
    };                
    var result = StringUtil.convertEncodedString(plainText, options);
    
    return result;
}

hidden function convertByteArrayToString(byteArray as ByteArray ) as String {
    var options = {
        :fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
        :toRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
        :encoding => StringUtil.CHAR_ENCODING_UTF8
    };              
    var result = StringUtil.convertEncodedString(byteArray, options);
    
    return result;
}

function decryptKey(deviceId as String) as String {
    var key = convertStringToByteArray("ec41e0ef9dc3469bbb0cd7849e00a0e7");
    System.println(key.toString());
    var iv = convertStringToByteArray("0000000000000000");

    var cipher = new Toybox.Cryptography.Cipher({
        :algorithm => Toybox.Cryptography.CIPHER_AES256,
        :mode => Toybox.Cryptography.MODE_CBC,
        :key => key,
        :iv => iv
    });

    var encryptedString = convertStringToByteArray(deviceId);
    var encryptedBytes = cipher.decrypt(encryptedString);
    var result = convertByteArrayToString(encryptedBytes);
    return result;
}


 But, when I call mu function like this:

var originalPlainText = decryptKey("8qyw5f3Ufmx42DEq+oezC/YkWaxwNWlMYPqMor5reYLUThgXtbaeYrYHhY/jDWNB");

I get an exception in the 
convertByteArrayToString function in var result = StringUtil.convertEncodedString(byteArray, options);.

> Error: System Error
> Details: Failed invoking <symbol>

I can't see anything wrong, but perhaps I'm doing something wrong, but I should get back my initial plain text ce5f33a8-4a88-42ed-8dbc-cfedd118ab4b.

Any take on this?

Thanks in advance for the help.
/Juan
  • I tried your code and example call in the simulator and I got the same error.

    As a test, in convertByteArrayToString I changed toRepresentation to StringUtil.REPRESENTATION_STRING_HEX. Now there's no crash, and this is the result of printing originalPlainText:

    66f5549d2a8f6deaacb6fa5981bfb9ddea3eec48f04b8a27c2c96db2a08b49370d0f9d375ec41e271416e758fbc5248002e2ffbadfe32c21b3a3a34c69b3efd6

    If I paste that into a hex-to-text converter after adding delimiters between the bytes, most of the output is unprintable.

    So I think the reason it's crashing is because convertEncodedString is unable to convert the provided input to plain text. Too bad it causes a System Error as opposed to throwing an exception tho.

    https://www.rapidtables.com/convert/number/hex-to-ascii.html

    66 f5 54 9d 2a 8f 6d ea ac b6 fa 59 81 bf b9 dd ea 3e ec 48 f0 4b 8a 27 c2 c9 6d b2 a0 8b 49 37 0d 0f 9d 37 5e c4 1e 27 14 16 e7 58 fb c5 24 80 02 e2 ff ba df e3 2c 21 b3 a3 a3 4c 69 b3 ef d6


  • Thanks for you answer. Yes I have tried the same, and got the same thing. I don't actually know what's going on with that encrypt function from Cipher. So what I'm trying to implement is a sort of "unlock code" in the app settings to give "vip" access to some part of my watch face features. So I'm exploring HashBasedMessageAuthenticationCode, and doing something like this:

    var HMACOptions = {
    	:algorithm => Cryptography.HASH_SHA256,
    	:key => key
    };
    
    var HMAC = new Cryptography.HashBasedMessageAuthenticationCode(HMACOptions);
    HMAC.update(plainText);
    var encryptedBytes = HMAC.digest();
    
    var result = StringUtil.convertEncodedString(encryptedBytes, {
        :fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
        :toRepresentation => StringUtil.REPRESENTATION_STRING_HEX,
        :encoding => StringUtil.CHAR_ENCODING_UTF8
    });


    At the end, result will have the value that will match with the unlock code. I'm avaluating this approach and so far is working.

    Thank you.

    /Juan

  • And btw, do you know how to get the watch face installation date? To be able to give a kind of trial period starting from the installation date.

  • Great, that's exactly what I needed. My plan is to store the current date in Application.Storage when this event is triggered. I'll then compare against that date to determine if the trial period has ended, then check if the user has provided an unlock key to access VIP features.

    What do you think of this approach? Is it possible to trigger onAppInstall from the simulator?

  • It does trigger for a new install to the sim.  But most of the time you'll see onAppUpdate.  Note..  You need the background permission for these.

  • But I just need the first time installation date, so I should use onAppInstall right? In what part of the sim can I trigger that event?

    I have searched in all options but nothing, neither in the Background events menu:

  • You don't have to do anything.  See if you can get onAppUpdate to trigger first, as with that you see it every time you run the app.  For onAppInstall, you have to keep resetting the sim....

    Start with

    function onAppUpdate() {

    System.println("update");

    }

    in your AppBase

  • Crystal clear, my friend!

    Could you clarify the difference between Application.Storage and Application.Properties when it comes to app installation and updates? My understanding is that Storage persists across installations, while Properties might reset when a new watch face version is installed. Is that correct?

  • Yes.  I use Storage for things without app-settings, as would be the case here.  You want to store the value in Storage.

    I'd probably use

    Time.now().value()

    which is the Unix time in seconds, and if you have a trial period of say 2 days, which is 172800 seconds. you check the current Time.now().value() against the saved value, and if the difference is more than 172800, the trial period is over.