16

I have a string as follows:

  • Its length is 10.
  • It represents base 36 and as such includes digits and uppercase letters.
  • The origin of the string is a sequence being generated by the database, (i.e. from 1 and up), that is being converted to base 36.

My problem is that the results of the conversion to base 36 conversion are also consecutive/sequential; for example:

    ID: 1402 -> 000000012Y    
    ID: 1403 -> 000000012Z    
    ID: 1404 -> 0000000130   
    ID: 1404 -> 0000000131  
    ID: 1404 -> 0000000132

I'm looking for a short simple algorithm that can mix the base 36 result where:

  • I can use only the allowed base 36 chars, (digits and uppercase letters).
  • The algorithm is just for obfuscating/mixing the base 36 string; I don't need encryption etc.
  • The main issue here is that the result won't be consecutive.
  • I need to have the ability to deobfuscate/demix the obfuscation result.

I tried it with some shifting chars logic but I'm stuck in the consecutive result issue.
I guess I need to add some mathematical aspect here.

I would appreciate any ideas as simple as possible and if possible then with code example.

Thanakron Tandavas
  • 5,615
  • 5
  • 28
  • 41
boomboom
  • 199
  • 1
  • 3
  • 9
  • What format the base 36 string is to be stored in? – suspectus May 05 '13 at 12:26
  • @suspectus the base 36 length is 10 chars and I'm trying to get a 10 chars string that is obfuscated and which I can de-obfuscate to the original base 36; the obfuscated string will be kept in the database as VARCHAR; I can't use additional chars other then the ones I mentioned above and of course the obfuscated string should be unique. To be even more clearer - I'm not looking for anything complicated, I just want to get rid of the consecutive issue I described above. I hope I'm clearer now and thanks in advance for your help !!! – boomboom May 05 '13 at 13:04
  • you mention that "this is a client requirement" in a comment to an answer. Exactly what is the requirement, and what is the purpose of the requirement? Your question seems... odd. Context would help answer in the direction that your client actually wants. – atk May 09 '13 at 04:18

5 Answers5

26

What about having just an array with the 36 characters in a random order? Something like a One-time pad encryption but with a fixed pad:

static String source="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static String target="Q5A8ZWS0XEDC6RFVT9GBY4HNU3J2MI1KO7LP";

public static String obfuscate(String s) {
    char[] result= new char[10];
    for (int i=0;i<s.length();i++) {
        char c=s.charAt(i);
        int index=source.indexOf(c);
        result[i]=target.charAt(index);
    }

    return new String(result);
}

public static String unobfuscate(String s) {
    char[] result= new char[10];
    for (int i=0;i<s.length();i++) {
        char c=s.charAt(i);
        int index=target.indexOf(c);
        result[i]=source.charAt(index);
    }

    return new String(result);
}

So a 10 characters string like "HELLO12345" becomes "0ZCCF2MI1K". Obfuscated, but not encrypted

Pablo Lozano
  • 10,122
  • 2
  • 38
  • 59
  • Very helpful, with a string scrambling program, it can be perfectly implemented. – amonxu Dec 17 '19 at 02:07
  • I would recommend using StringBuilder and append chars instead of creating a string in size 10. also missing here support for lower case and special characters like .,@: – Brachi Mar 11 '21 at 09:14
  • @Brachi This code was just a proof of concept for the constraints given by the OP: always a length of 10 chars, only 36 different chars. For a wider scope you'll need a different approach – Pablo Lozano Mar 11 '21 at 09:34
25

Are you looking for something like this?

import java.util.Locale;

public class Obfuscate {

    //adjust to suit:
    final static int feistelRounds = 4;
    final static int randRounds = 4;
    final static int seed = 12345;

    // modulus for half a string:
    final static int mod = 60466176; //36^5

    private static int f (int x) {
        // http://en.wikipedia.org/wiki/Linear_congruential_generator
        final int a = 12+1;
        final int c = 1361423303;
        x = (x + seed) % mod;
        int r = randRounds;
        while (r-- != 0) {
            x = (a*x+c) % mod;
        }
        return x;
    }

    public static String obfuscate (int i) {
        int a = i / mod;
        int b = i % mod;
        int r = feistelRounds;
        while (r-- != 0) {
            a = (a + f(b)) % mod;
            b = (b + f(a)) % mod;
        }
        return pad5(Integer.toString(a, 36)) + pad5(Integer.toString(b, 36));
    }

    public static int illuminate (String s) {
        int a = Integer.valueOf(s.substring(0,5),36);
        int b = Integer.valueOf(s.substring(5,10),36);
        int r = feistelRounds;
        while (r-- != 0) {
            b = (b - f(a)) % mod;
            a = (a - f(b)) % mod;
        }
        // make the modulus positive:
        a = (a + mod)%mod;
        b = (b + mod)%mod;

        return a*mod+b;
    }

    public static String pad5(String s) {
        return String.format("%5s", s).replace(' ', '0').toUpperCase(Locale.ENGLISH);
    }

    public static String pad10(String s) {
        return String.format("%10s", s).replace(' ', '0').toUpperCase(Locale.ENGLISH);
    }

    // demonstration
    public static void main(String[] args) {
        for (int i = 0; i<20; i++) {
            System.out.printf("%08d -> %s -> %08d\n", i, obfuscate(i), illuminate(obfuscate(i)));
        }
    }
}

output:

00000000 -> P2TH9ZW2VI -> 00000000
00000001 -> G47GI9ZR9S -> 00000001
00000002 -> 75LFRK3FO2 -> 00000002
00000003 -> Y6ZF0U742C -> 00000003
00000004 -> P8DE94ASGM -> 00000004
00000005 -> G9RDIEEGUW -> 00000005
00000006 -> 7B5CROI596 -> 00000006
00000007 -> YCJC0YLTNG -> 00000007
00000008 -> PDXB98PI1Q -> 00000008
00000009 -> GFBAIIT6G0 -> 00000009
00000010 -> 7GP9RSWUUA -> 00000010
00000011 -> YI39030J8K -> 00000011
00000012 -> PJH89D47MU -> 00000012
00000013 -> GKV7IN7W14 -> 00000013
00000014 -> 7M96RXBKFE -> 00000014
00000015 -> YNN607F8TO -> 00000015
00000016 -> PP159HIX7Y -> 00000016
00000017 -> GQF4IRMLM8 -> 00000017
00000018 -> 7RT3R1QA0I -> 00000018
00000019 -> YT730BTYES -> 00000019

Basically, this is a toy, totally non-secure, though fun to write, encryption algorithm. (Encryption really is what you asked for --- output that's unintelligible to others but reversible by you.) I've implemented a Feistel network (http://en.wikipedia.org/wiki/Feistel_cipher) using a simple prng as the f function.

The results are pretty, though, right? DES, as suggested above, would be more secure. But, if you'd rather reinvent the wheel (I struggle with that impulse a bit myself) and real security isn't a concern, this is a reasonable place to start. BTW, DES is also based on a Feistel network.

Actually, a non-encryption-based solution might exist, depending on your requirements. If this is, say, a coupon code that needs to be checked but not guessed, I'd just create a table in my database relating the id to a randomly-generated 10 character code (or add the code column to an existing table of coupons) and look them up as they come in. This would of course require the encoding and recovering software to have access to the same database, or to be able to communicate.

maybeWeCouldStealAVan
  • 15,492
  • 2
  • 23
  • 32
  • 1
    This is really helpful. Is there any general version of the code where I can choose the no. of characters for the ID? And to include the lowercase letters as well. That makes the base as 62. – Dany Jun 05 '15 at 01:56
8

This is a generic solution, this a very fast algorithm that can handle any string in any encoding.

Source code

public class Translator {

    private static final String key = "Zx" + Math.log(2) / 3;

    public static String obfuscate(String s) {
        char[] result = new char[s.length()];
        for (int i = 0; i < s.length(); i++) {
            result[i] = (char) (s.charAt(i) + key.charAt(i % key.length()));
        }

        return new String(result);
    }

    public static String unobfuscate(String s) {
        char[] result = new char[s.length()];
        for (int i = 0; i < s.length(); i++) {
            result[i] = (char) (s.charAt(i) - key.charAt(i % key.length()));
        }

        return new String(result);
    }
}

Usage

String obfuscate = Translator.obfuscate("Hi there");
System.out.println(obfuscate + " - " + Translator.unobfuscate(obfuscate));

Output:

¢áP¢£ - Hi there
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
4

Just reverse the bits on your counter before doing the base36 encoding. Something like this

public static void main(String[] args) {
    for (int i = 1400; i < 1420; i++) {
        String base36 = Integer.toString(i, 36);
        String reverse = Integer.toString(Integer.reverse(i << 1), 36);

        System.out.println("i: " + i + "  base36: " + base36 + 
                                       "  reverse: " + reverse);
    }
}

Result:

i: 1400  base36: 12w  reverse: 48ya68
i: 1401  base36: 12x  reverse: m08ao0
i: 1402  base36: 12y  reverse: d4laf4
i: 1403  base36: 12z  reverse: uvvaww
i: 1404  base36: 130  reverse: 8orsao
i: 1405  base36: 131  reverse: qg1ssg
i: 1406  base36: 132  reverse: hkesjk
i: 1407  base36: 133  reverse: zbot1c
i: 1408  base36: 134  reverse: 8464g
i: 1409  base36: 135  reverse: hze6m8
i: 1410  base36: 136  reverse: 93r6dc
i: 1411  base36: 137  reverse: qv16v4
i: 1412  base36: 138  reverse: 4nxo8w
i: 1413  base36: 139  reverse: mf7oqo
i: 1414  base36: 13a  reverse: djkohs
i: 1415  base36: 13b  reverse: vauozk
i: 1416  base36: 13c  reverse: 2g0x6o
i: 1417  base36: 13d  reverse: k7axog
i: 1418  base36: 13e  reverse: bbnxfk
i: 1419  base36: 13f  reverse: t2xxxc
Ebbe M. Pedersen
  • 7,250
  • 3
  • 27
  • 47
2

Unless this is a homework assignment I'd suggest you to use Base64 encoding: new sun.misc.BASE64Encoder().encode(string.getBytes()).

This does not encrypt string but makes it unreadable.

If you really want to encrypt the string use java cryptography API, e.g:

        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(Cipher.ENCRYPT_MODE, password);
        String encrypedStr = base64encoder.encode(cipher.doFinal(cleartext));

Now encryptedString is encrypted and stored in base64 format.

You can easily find how to decrypt the string back. Good luck.

AlexR
  • 114,158
  • 16
  • 130
  • 208
  • Thanks for the answer but this is not homework; I need to get from the base 36 string an obfuscated string that can have digits and uppercase letters; this is our client requirement so I can't use your suggestion. I have looked a lot for an answer to this bit with no luck so far – boomboom May 05 '13 at 12:36
  • 3
    argh at string.getBytes() with no encoding specified :// – Andrea Ligios May 14 '13 at 12:57