如何生成超过 2^30 个组合的随机唯一字符串。我也想扭转这个过程。这可能吗?

发布于 2024-10-21 11:55:04 字数 7507 浏览 6 评论 0原文

我有一个包含 3 个元素的字符串:

  • 3 位代码(例如:SIN、ABD、SMS 等)
  • 1 位代码类型(例如:1、2、3 等)
  • 3 位数字(例如:500、123) , 345)

示例字符串:SIN1500、ABD2123、SMS3345 等。

我想生成一个唯一的 10 位字母数字和区分大小写的字符串(仅允许 0-9/az/AZ),超过 2^30(大约 1十亿)提供的每个字符串的唯一组合。生成的代码必须具有特定的算法,以便我可以反转该过程。例如:

public static void main(String[] args) {
    String test = "ABD2123";
    String result = generateData(test);
    System.out.println(generateOutput(test)); //for example, the output of this is: 1jS8g4GDn0
    System.out.println(generateOutput(result)); //the output of this will be ABD2123 (the original string supplied)
}

我想问的是java中是否有任何想法/示例/库可以做到这一点?或者至少有关于我应该在谷歌上放置什么关键字的提示?

我尝试使用关键字 java checksum、rng、security、random number 等进行谷歌搜索,还尝试查看一些随机数解决方案(java SecureRandom、xorshift RNG、java.util.zip 的 checksum 等),但我似乎找不到一?

谢谢!

编辑:

我对该程序的用例是生成某种唯一的优惠券编号以供特定客户使用。

提供的字符串将包含 3 位数的公司 ID 代码、1 位数的凭证类型代码以及 3 位数的凭证名称。我还尝试添加 3 个随机字母数字(因此最后的数字是 7 + 3 位数字 = 10 位数字)。这是我到目前为止所做的,但结果不是很好(只有大约10万个组合):

public static String in ="somerandomstrings";
public static String out="someotherrandomstrings";

public static String encrypt(String kata)
throws Exception {
    String result="";
    String ina=in;
    String outa=out;
    Random ran = new Random();
    Integer modulus=in.length();
    Integer offset= ((Integer.parseInt(Utils.convertDateToString(new Date(), "SS")))+ran.nextInt(60))/2%modulus;

    result=ina.substring(offset, offset+1);
    ina=ina+ina;
    ina=ina.substring(offset, offset+modulus);
    result=result+translate(kata, ina, outa);

    return result;
}

编辑:

很抱歉我忘记添加“翻译”功能:

    public static String translate(String kata,String  seq1, String  seq2){
    String result="";
    if(kata!=null&seq1!=null&seq2!=null){
        String[] a=kata.split("");
        for (int j = 1; j < a.length; j++) {
            String b=a[j];                      
            String[]seq1split=seq1.split("");
            String[]seq2split=seq2.split("");
            int hint=seq1.indexOf(b)+1;

            String sq="";
            if(seq1split.length>hint)
                sq=seq1split[hint];
            String sq1="";
            if(seq2split.length>hint)
                sq1=seq2split[hint];

            b=b.replace(sq, sq1);

            result=result+b;
        }
    }
    return result;
}

>编辑:

好的,几天后我正在努力转换@sarnold提供的Ruby代码,这是我想出的代码:

public class Test {

static char START_A = "A".charAt(0);
static char START_a = "a".charAt(0);
static char START_0 = "0".charAt(0);
static int CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
static int RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

public static void main(String[] args) {

    String[] test = new String[]{
            //"AAA0000", "SIN1500", "ABD2123", "SMS3345", "ZZZ9999",
            //"ABD2123", "ABD2123", "ABD2123", "ABD2123", "ABD2123"
            "ABD2123"
            };

    for(String t : test){
        long c = compress(t);
        long a = add_random(c);
        String output = to_output(a);
        long input = from_output(output);

        String[] new_c_r = remove_random(input);
        String u = uncompress(Long.valueOf(new_c_r[0]));

        System.out.println("Original input: " + t);
        System.out.println("    compressed: " + c);
        System.out.println("    after adding random amount: " + a);
        System.out.println("    *output: " + output);
        System.out.println("    *input: " + input);
        System.out.println("    random amount added: " + new_c_r[1]);
        System.out.println("    after removing random amount: " + new_c_r[0]);
        System.out.println("    uncompressed: " + u);
        System.out.println("-----------------------------------------------------------------");
    }

}

public static long compress(String line){ //7 character
    char a = line.charAt(0);
    char b = line.charAt(1);
    char c = line.charAt(2);
    char h = line.charAt(3);
    char i = line.charAt(4);
    char j = line.charAt(5);
    char k = line.charAt(6);

    long small_a = (long) a - START_A;
    long small_b = (long) b - START_A;
    long small_c = (long) c - START_A;
    long letters = (small_a * 26 * 26) + (small_b * 26) + small_c;
    long numbers = letters * 10000 + h * 1000 + i*100 + j*10 + k;
    return numbers;
}

public static String uncompress(long number){
    long k = number % 10;
    number /= 10;
    long j = number % 10;
    number /= 10;
    long i = number % 10;
    number /= 10;
    long h = number % 10;
    number /= 10;
    long small_c = number % 26;
    number /= 26;
    long small_b = number % 26;
    number /= 26;
    long small_a = number % 26;
    number /= 26;

    if (number != 0) throw new RuntimeException("input wasn't generated with compress()");

    long a = small_a + START_A;
    long b = small_b + START_A;
    long c = small_c + START_A;

    StringBuffer result = new StringBuffer();
    result.append((char) a).append((char) b).append((char) c).append(h).append(i).append(j).append(k);

    return result.toString();
}

public static long add_random(long number){
    return (((long) (Math.random()* Math.pow(2, 31))) << 28) + number;
}

public static String[] remove_random(long number){
    return new String[]{String.valueOf(number & CODEMASK), String.valueOf(number & RANDOMMASK)};
}

public static String to_output(long number){
    List<Character> a = new ArrayList<Character>();
    do{
        a.add(transform_out(number % 62));
        number /= 62;
    }while(number > 0);

    Collections.reverse(a);

    StringBuffer result = new StringBuffer();
    for(int i=0; i<a.size(); i++){
        Character s = (Character) a.get(i);
        result.append(s);
    }

    return result.toString();
}

public static long from_output(String string){
    long num = 0;
    for(char c : string.toCharArray()){
        num *= 62;
        num += transform_in(c);
    }
    return num;
}

public static char transform_out(long small){
    long out;

    if (small < 0 || small > 61){
        throw new RuntimeException("small should be between 0 and 61, inclusive");
    }
    if(small < 26){
        out = START_A + small;
    }else if(small < 52){
        out = START_a + (small-26);
    }else{
        out = START_0 + (small-52);
    }
    return (char) out;
}


public static long transform_in(char c){
    if(!String.valueOf(c).matches("[a-zA-Z0-9]")){ 
        throw new RuntimeException("char should be A-Z, a-z, or 0-9, inclusive");
    }
    long num = (long) c;

    long out;
    if(num >= START_A && num <= START_A+26) out = num-START_A;
    else if(num >= START_a && num <= START_a+26) out = (num-START_a) + 26;
    else if(num >= START_0 && num <= START_0+10) out = (num-START_0) + 52;
    else throw new RuntimeException("Salah, bego!");

    return out;
}}

但我似乎无法使这个代码正确,结果是这样的:

Original input: ABD2123
compressed: 345451
after adding random amount: 62781252268541291
*output: EnhZJdRFaj
*input: 62781252268541291
random amount added: 0
after removing random amount: 345451
uncompressed: ABI5451

您可能已经注意到“压缩”和“未压缩”字段没有显示相同的数量。 “随机添加量”字段也始终返回 0 值。 有没有人可以帮我看看这段代码哪里错了?

很抱歉,如果将此代码放入此线程中是错误的,我将创建另一个线程。

谢谢你, 优素福

I have a string which contains 3 elements:

  • a 3 digit code (example: SIN, ABD, SMS, etc)
  • a 1 digit code type (example: 1, 2, 3, etc)
  • a 3 digit number (example: 500, 123, 345)

Example string: SIN1500, ABD2123, SMS3345, etc..

I wanted to generate a UNIQUE 10 digit alphanumeric and case sensitive string (only 0-9/a-z/A-Z is allowed), with more than 2^30 (about 1 billion) unique combination per string supplied. The generated code must have a particular algorithm so that I can reverse the process. For example:

public static void main(String[] args) {
    String test = "ABD2123";
    String result = generateData(test);
    System.out.println(generateOutput(test)); //for example, the output of this is: 1jS8g4GDn0
    System.out.println(generateOutput(result)); //the output of this will be ABD2123 (the original string supplied)
}

What I wanted to ask is is there any ideas/examples/libraries in java that can do this? Or at least any hint on what keyword should I put on Google?

I tried googling using the keyword java checksum, rng, security, random number, etc and also tried looking at some random number solution (java SecureRandom, xorshift RNG, java.util.zip's checksum, etc) but I can't seem to find one?

Thanks!

EDIT:

My use case for this program is to generate some kind of unique voucher number to be used by specific customers.

The string supplied will contains 3 digit code for company ID, 1 digit code for voucher type, and a 3 digit number for the voucher nominal. I also tried adding 3 random alphanumeric (so the final digit is 7 + 3 digit = 10 digit). This is what I've done so far, but the result is not very good (only about 100 thousand combination):

public static String in ="somerandomstrings";
public static String out="someotherrandomstrings";

public static String encrypt(String kata)
throws Exception {
    String result="";
    String ina=in;
    String outa=out;
    Random ran = new Random();
    Integer modulus=in.length();
    Integer offset= ((Integer.parseInt(Utils.convertDateToString(new Date(), "SS")))+ran.nextInt(60))/2%modulus;

    result=ina.substring(offset, offset+1);
    ina=ina+ina;
    ina=ina.substring(offset, offset+modulus);
    result=result+translate(kata, ina, outa);

    return result;
}

EDIT:

I'm sorry I forgot to put the "translate" function :

    public static String translate(String kata,String  seq1, String  seq2){
    String result="";
    if(kata!=null&seq1!=null&seq2!=null){
        String[] a=kata.split("");
        for (int j = 1; j < a.length; j++) {
            String b=a[j];                      
            String[]seq1split=seq1.split("");
            String[]seq2split=seq2.split("");
            int hint=seq1.indexOf(b)+1;

            String sq="";
            if(seq1split.length>hint)
                sq=seq1split[hint];
            String sq1="";
            if(seq2split.length>hint)
                sq1=seq2split[hint];

            b=b.replace(sq, sq1);

            result=result+b;
        }
    }
    return result;
}

EDIT:

Okay, after a few days I'm currently struggling to convert the Ruby code provided by @sarnold, this is the code I've come up with :

public class Test {

static char START_A = "A".charAt(0);
static char START_a = "a".charAt(0);
static char START_0 = "0".charAt(0);
static int CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
static int RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

public static void main(String[] args) {

    String[] test = new String[]{
            //"AAA0000", "SIN1500", "ABD2123", "SMS3345", "ZZZ9999",
            //"ABD2123", "ABD2123", "ABD2123", "ABD2123", "ABD2123"
            "ABD2123"
            };

    for(String t : test){
        long c = compress(t);
        long a = add_random(c);
        String output = to_output(a);
        long input = from_output(output);

        String[] new_c_r = remove_random(input);
        String u = uncompress(Long.valueOf(new_c_r[0]));

        System.out.println("Original input: " + t);
        System.out.println("    compressed: " + c);
        System.out.println("    after adding random amount: " + a);
        System.out.println("    *output: " + output);
        System.out.println("    *input: " + input);
        System.out.println("    random amount added: " + new_c_r[1]);
        System.out.println("    after removing random amount: " + new_c_r[0]);
        System.out.println("    uncompressed: " + u);
        System.out.println("-----------------------------------------------------------------");
    }

}

public static long compress(String line){ //7 character
    char a = line.charAt(0);
    char b = line.charAt(1);
    char c = line.charAt(2);
    char h = line.charAt(3);
    char i = line.charAt(4);
    char j = line.charAt(5);
    char k = line.charAt(6);

    long small_a = (long) a - START_A;
    long small_b = (long) b - START_A;
    long small_c = (long) c - START_A;
    long letters = (small_a * 26 * 26) + (small_b * 26) + small_c;
    long numbers = letters * 10000 + h * 1000 + i*100 + j*10 + k;
    return numbers;
}

public static String uncompress(long number){
    long k = number % 10;
    number /= 10;
    long j = number % 10;
    number /= 10;
    long i = number % 10;
    number /= 10;
    long h = number % 10;
    number /= 10;
    long small_c = number % 26;
    number /= 26;
    long small_b = number % 26;
    number /= 26;
    long small_a = number % 26;
    number /= 26;

    if (number != 0) throw new RuntimeException("input wasn't generated with compress()");

    long a = small_a + START_A;
    long b = small_b + START_A;
    long c = small_c + START_A;

    StringBuffer result = new StringBuffer();
    result.append((char) a).append((char) b).append((char) c).append(h).append(i).append(j).append(k);

    return result.toString();
}

public static long add_random(long number){
    return (((long) (Math.random()* Math.pow(2, 31))) << 28) + number;
}

public static String[] remove_random(long number){
    return new String[]{String.valueOf(number & CODEMASK), String.valueOf(number & RANDOMMASK)};
}

public static String to_output(long number){
    List<Character> a = new ArrayList<Character>();
    do{
        a.add(transform_out(number % 62));
        number /= 62;
    }while(number > 0);

    Collections.reverse(a);

    StringBuffer result = new StringBuffer();
    for(int i=0; i<a.size(); i++){
        Character s = (Character) a.get(i);
        result.append(s);
    }

    return result.toString();
}

public static long from_output(String string){
    long num = 0;
    for(char c : string.toCharArray()){
        num *= 62;
        num += transform_in(c);
    }
    return num;
}

public static char transform_out(long small){
    long out;

    if (small < 0 || small > 61){
        throw new RuntimeException("small should be between 0 and 61, inclusive");
    }
    if(small < 26){
        out = START_A + small;
    }else if(small < 52){
        out = START_a + (small-26);
    }else{
        out = START_0 + (small-52);
    }
    return (char) out;
}


public static long transform_in(char c){
    if(!String.valueOf(c).matches("[a-zA-Z0-9]")){ 
        throw new RuntimeException("char should be A-Z, a-z, or 0-9, inclusive");
    }
    long num = (long) c;

    long out;
    if(num >= START_A && num <= START_A+26) out = num-START_A;
    else if(num >= START_a && num <= START_a+26) out = (num-START_a) + 26;
    else if(num >= START_0 && num <= START_0+10) out = (num-START_0) + 52;
    else throw new RuntimeException("Salah, bego!");

    return out;
}}

but I can't seem to make this code right, the result is like this :

Original input: ABD2123
compressed: 345451
after adding random amount: 62781252268541291
*output: EnhZJdRFaj
*input: 62781252268541291
random amount added: 0
after removing random amount: 345451
uncompressed: ABI5451

as you've probably noticed the "compressed" and the "uncompressed" field didn't show the same amount. The "random amount added" field is also always returning 0 value.
Is there anyone who can help see where I'm wrong in this code?

I'm sorry if it's a mistake to put this code in this thread I will create another thread.

Thank You,
Yusuf

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

下壹個目標 2024-10-28 11:55:04

你的要求很不明确。因此,我将把我的答案限制为一般性:

有一些称为加密哈希函数的函数,它们将从任意字节序列映射到“哈希”,其属性是任何两个相关输入给出相同输出的概率为小得几乎消失。然而,加密哈希函数(根据设计)是不可逆的……根据设计。

从字符串的一个“空间”到另一个可逆的映射可以通过两种方式实现:

  • 您可以生成任意字符串作为映射的字符串,并使用哈希表等数据结构来存储正向和反向

  • 您可以使用加密哈希函数来生成映射的字符串,并使用哈希表等数据结构来存储反向映射。

  • 您可以使用可逆函数在原始字符串和映射字符串之间进行转换。这存在一个问题,即映射可能很容易进行逆向工程。

后者的一个例子可能是将原始字符串转换为字节,然后对其进行 Base64 编码。或者更简单的是,您可以在输入字符串中的每个字符之间插入一个随机字符。 (显然,像这样的转换可以在几分钟内进行逆向工程......给出足够的例子。因此人们不得不怀疑这种方法的智慧。)

如果没有更好的要求,就不清楚这些方法中的哪一种(如果有的话)是什么你需要。

Your requirements are very unclear. So I'm going to restrict my answer to generalities:

There are functions called cryptographic hash functions that will map from an arbitrary sequence of bytes to a "hash", with the property that the probability of any two related inputs giving the same output is vanishingly small. However, cryptographic hash functions are (by design) not reversible ... by design.

A mapping from one "space" of Strings to another that is reversible, can be implemented in two ways:

  • You can generate arbitrary Strings as the mapped Strings, and use data structures such as a hash tables to store the forward and reverse mappings.

  • You can use a cryptographic hash function to generate the mapped Strings, and use data structures such as a hash tables to store the reverse mappings.

  • You can use a reversible function to transform between the original and mapped Strings. This has the problem that the mapping is likely to be be easy to reverse engineer.

An example of the latter might be to turn the original String into bytes and then base64 encode it. Or even more trivially, you could insert a random character between each character in the input string. (Obviously, transformations like these can be reverse engineered in a few minutes ... given enough examples. So one has to doubt the wisdom of this approach.)

Without better requirements, it is unclear which (if any) of these approaches is what you need.

渔村楼浪 2024-10-28 11:55:04

我编写了一个 Ruby 工具,可以满足您的需要。 (抱歉,它不是 Java,但我的 Java 已经有十多年的历史了。但是总体思路应该可以毫无麻烦地移植到 Java。)

简而言之,给定的输入字符串(7 个字节)被压缩为 0175_760_000(28 位)。前面添加了一个 32 位随机数,形成一个 60 位整数,该整数被编码为 10 个字符的输出字符串。

我之前的数学是错误的;由于您的输入只有 28 位长,而所需的输出长为 60 位,因此还剩下 32 位用于添加大约 20 亿个随机排列。我在进行计算时输入错误。

#!/usr/bin/ruby -w
START_A = "A"[0]
START_a = "a"[0]
START_0 = "0"[0]
CODEMASK = ((1 << 28) - 1) # turn on lower 28 bits
RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK # turn on upper 32 bits


def compress(line)
    a, b, c, h, i, j, k = line.chomp().each_char().entries()
    a, b, c = a[0], b[0], c[0]
    small_a, small_b, small_c = a - START_A, b - START_A, c - START_A
    letters = (small_a * 26**2) + (small_b * 26) + small_c
    h, i, j, k = Integer(h), Integer(i), Integer(j), Integer(k)
    number = letters * 10_000 + h*1000 + i*100 + j*10 + k
end

def uncompress(number)
    k = number % 10
    number /= 10
    j = number % 10
    number /= 10
    i = number % 10
    number /= 10
    h = number % 10
    number /= 10
    small_c = number % 26
    number /= 26
    small_b = number % 26
    number /= 26
    small_a = number % 26
    number /= 26
    if (number != 0)
            raise "input wasn't generated with compress()"
    end
    a, b, c = small_a + START_A, small_b + START_A, small_c + START_A
    [a.chr(), b.chr(), c.chr(), h.to_s(), i.to_s(), j.to_s(), k.to_s()].join()
end

def add_random(number)
    (rand(2**31) << 28) + number
end 

def remove_random(number)
    [number & CODEMASK, number & RANDOMMASK]
end

def to_output(number)
    a = []
    begin
            a << transform_out(number % 62)
            number /= 62
    end while(number > 0)
    a.reverse().join()
end

def from_output(string)
    num = 0
    string.each_char() do |c|
            num *= 62
            num += transform_in(c)
    end     
    num
end

def transform_out(small)
    if (small < 0 || small > 61)
            raise "small should be between 0 and 61, inclusive"
    end
    if (small < 26)
            out = START_A+small
    elsif (small < 52)
            out = START_a+(small-26)
    else
            out = START_0+(small-52)
    end
    out.chr()
end

def transform_in(char)
    if (/^[A-Za-z0-9]$/ !~ char)
            raise "char should be A-Z, a-z, or 0-9, inclusive"
    end
    num = char[0]
    out = case num
    when START_A .. START_A+26 then num - START_A
    when START_a .. START_a+26 then (num - START_a) + 26
    when START_0 .. START_0+10 then (num - START_0) + 52
    end

    out
end


begin
    while(line = DATA.readline()) do
            line.chomp!()
            c = compress(line)
            a = add_random(c)
            output = to_output(a)
            input = from_output(output)
            new_c, r = remove_random(input)
            u = uncompress(new_c)

            printf("original input: %s\n compressed: %d\n after adding random amount: %d\n *output: %s\n *input: %s\n random amount added: %d\n after removing random amount: %d\nuncompressed: %s\n", line, c, a, output, input, r, new_c, u)
    end
rescue EOFError => e
end


__END__
AAA0000
SIN1500
ABD2123
SMS3345
ZZZ9999

使用底部给出的五个输入运行程序会产生以下输出:

$ ./compress.rb
original input: AAA0000
 compressed: 0
 after adding random amount: 508360097408221184
 *output: liSQkzXL1G
 *input: 508360097408221184
 random amount added: 508360097408221184
 after removing random amount: 0
uncompressed: AAA0000
original input: SIN1500
 compressed: 123891500
 after adding random amount: 421470683267231532
 *output: fIVFtX9at2
 *input: 421470683267231532
 random amount added: 421470683143340032
 after removing random amount: 123891500
uncompressed: SIN1500
original input: ABD2123
 compressed: 292123
 after adding random amount: 414507907112269083
 *output: emb6JfDhUH
 *input: 414507907112269083
 random amount added: 414507907111976960
 after removing random amount: 292123
uncompressed: ABD2123
original input: SMS3345
 compressed: 124983345
 after adding random amount: 383242064398325809
 *output: cTPpccLAvn
 *input: 383242064398325809
 random amount added: 383242064273342464
 after removing random amount: 124983345
uncompressed: SMS3345
original input: ZZZ9999
 compressed: 175759999
 after adding random amount: 27149937199932031
 *output: CAVf14tiRh
 *input: 27149937199932031
 random amount added: 27149937024172032
 after removing random amount: 175759999
uncompressed: ZZZ9999

您真正要查找的行是原始输入*输出未压缩。您的客户端有原始输入行,使用to_output(add_random(compress(input)))后,您将获得十个字符的A-Za-z0 -9*output 行中输出。你把它交给用户,它是某种神奇的代币。然后,当需要验证它们时,您可以使用 remove_random(from_output(user_string)) 来发现添加到字符串中的随机值您可以传递给的整数解压缩()

一个非常重要的注意事项:输入AAA0000以明文形式存储在低28位中。随机数以明文形式存储在高 32 位中。这只是对原始输入的混淆,如果有人有两个输入和输出,那么他们不难发现该模式。哎呀,他们甚至可能在仅给出单个输入和输出的情况下正确猜测算法。

如果您需要它具有强大的加密性,那么您仍然需要做一些工作:)但这可能就像将中间 60 位数字与用户名的 rc4 输出或其他内容进行异或一样简单像那样。

简短说明

您的输入字符串可以被解释为整数,但是有一些变化:前三个“数字”采用 基数 26,接下来的四位数字采用基数 10数量将小于 175_760_000。唯一存储 0175_760_000 之间的数字需要 28 位。您的输出字符串也是一个数字,十位数字长,每个“数字”都是以 62 为基数。(想想 base64< /a> 但没有 -/=(用于填充)。)62 个可能的值和 10 个位置为您提供了最大值839_299_365_868_340_224,可以用60位表示。

输入字符串仅需要 28 位来表示,输出字符串有 60 位可用,并且剩下 32 位可用于存储随机 -生成的号码。如果我们将 32 位数字乘以 2^28(与左移 28 相同:1 << 28 ) 那么较低的 28 位将为原始输入的数字释放。

一旦我们计算出 60 位数字,我们就会以 62 进制表示法输出它以供人类使用。

要反转该过程,您可以对 62 基数进行解码以获得我们的 60 位数字;将60位数字拆分为低位28位输入数和高位32位随机数,然后输出28 code> 原始格式的位数:三个基数 26 位数字,后跟四个基数 10 位数字。

最终更新

Yusuf,将我的 Ruby 转换为 Java 的工作非常出色。我印象非常深刻,特别是考虑到您的 Java 版本看起来多么:您的版本更清晰。我很嫉妒,也很感动。 :)

我发现你的程序中仍然存在两个小错误:RANDOMMASK 被意外初始化为 0,因为 Java 执行算术移位语句时不会将所有操作数提升为最终数据类型。将 1 更改为 1L 会强制得到 1L << 60long;如果没有 L,则结果为 1 << 60 是一个 int,它的大小不足以容纳完整的数字。

此外,数字没有被正确压缩;我的 Ruby 代码将字符解析为整数,而您的 Java 代码将字符解释为整数。 (你的使用了字符的值;我的根据字符的 ascii含义 将字符转换为整数。我的并没有真正解析,因为它只是做了一个减法,但如果它是一个字符串,String.toInteger(character) 会做同样的事情,所以它很像解析。)

但是你的解压缩逻辑是正确的,并且由于不匹配,输出不正确。因此,我更改了您的代码以将数字解析为整数(并从 char 更改为 int 以消除毫无意义的警告)。

这是我必须在程序中更改才能使其工作的差异:

--- Compress.java.orig  2011-03-25 16:57:47.000000000 -0700
+++ Compress.java   2011-03-25 17:09:42.000000000 -0700
@@ -1,12 +1,12 @@
-import java.util.*
+import java.util.*;

 public class Compress {

 static char START_A = "A".charAt(0);
 static char START_a = "a".charAt(0);
 static char START_0 = "0".charAt(0);
-static int CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
-static int RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits
+static long CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
+static long RANDOMMASK = ((1L << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

 public static void main(String[] args) {

@@ -42,10 +42,10 @@
     char a = line.charAt(0);
     char b = line.charAt(1);
     char c = line.charAt(2);
-    char h = line.charAt(3);
-    char i = line.charAt(4);
-    char j = line.charAt(5);
-    char k = line.charAt(6);
+    int h = line.charAt(3) - START_0;
+    int i = line.charAt(4) - START_0;
+    int j = line.charAt(5) - START_0;
+    int k = line.charAt(6) - START_0;

     long small_a = (long) a - START_A;
     long small_b = (long) b - START_A;

现在是完整的源代码,以防万一更容易:)

import java.util.*;

public class Compress {

static char START_A = "A".charAt(0);
static char START_a = "a".charAt(0);
static char START_0 = "0".charAt(0);
static long CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
static long RANDOMMASK = ((1L << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

public static void main(String[] args) {

    String[] test = new String[]{
            //"AAA0000", "SIN1500", "ABD2123", "SMS3345", "ZZZ9999",
            //"ABD2123", "ABD2123", "ABD2123", "ABD2123", "ABD2123"
            "ABD2123"
            };

    for(String t : test){
        long c = compress(t);
        long a = add_random(c);
        String output = to_output(a);
        long input = from_output(output);

        String[] new_c_r = remove_random(input);
        String u = uncompress(Long.valueOf(new_c_r[0]));

        System.out.println("Original input: " + t);
        System.out.println("    compressed: " + c);
        System.out.println("    after adding random amount: " + a);
        System.out.println("    *output: " + output);
        System.out.println("    *input: " + input);
        System.out.println("    random amount added: " + new_c_r[1]);
        System.out.println("    after removing random amount: " + new_c_r[0]);
        System.out.println("    uncompressed: " + u);
        System.out.println("-----------------------------------------------------------------");
    }

}

public static long compress(String line){ //7 character
    char a = line.charAt(0);
    char b = line.charAt(1);
    char c = line.charAt(2);
    int h = line.charAt(3) - START_0;
    int i = line.charAt(4) - START_0;
    int j = line.charAt(5) - START_0;
    int k = line.charAt(6) - START_0;

    long small_a = (long) a - START_A;
    long small_b = (long) b - START_A;
    long small_c = (long) c - START_A;
    long letters = (small_a * 26 * 26) + (small_b * 26) + small_c;
    long numbers = letters * 10000 + h * 1000 + i*100 + j*10 + k;
    return numbers;
}

public static String uncompress(long number){
    long k = number % 10;
    number /= 10;
    long j = number % 10;
    number /= 10;
    long i = number % 10;
    number /= 10;
    long h = number % 10;
    number /= 10;
    long small_c = number % 26;
    number /= 26;
    long small_b = number % 26;
    number /= 26;
    long small_a = number % 26;
    number /= 26;

    if (number != 0) throw new RuntimeException("input wasn't generated with compress()");

    long a = small_a + START_A;
    long b = small_b + START_A;
    long c = small_c + START_A;

    StringBuffer result = new StringBuffer();
    result.append((char) a).append((char) b).append((char) c).append(h).append(i).append(j).append(k);

    return result.toString();
}

public static long add_random(long number){
    return (((long) (Math.random()* Math.pow(2, 31))) << 28) + number;
}

public static String[] remove_random(long number){
    return new String[]{String.valueOf(number & CODEMASK), String.valueOf(number & RANDOMMASK)};
}

public static String to_output(long number){
    List<Character> a = new ArrayList<Character>();
    do{
        a.add(transform_out(number % 62));
        number /= 62;
    }while(number > 0);

    Collections.reverse(a);

    StringBuffer result = new StringBuffer();
    for(int i=0; i<a.size(); i++){
        Character s = (Character) a.get(i);
        result.append(s);
    }

    return result.toString();
}

public static long from_output(String string){
    long num = 0;
    for(char c : string.toCharArray()){
        num *= 62;
        num += transform_in(c);
    }
    return num;
}

public static char transform_out(long small){
    long out;

    if (small < 0 || small > 61){
        throw new RuntimeException("small should be between 0 and 61, inclusive");
    }
    if(small < 26){
        out = START_A + small;
    }else if(small < 52){
        out = START_a + (small-26);
    }else{
        out = START_0 + (small-52);
    }
    return (char) out;
}


public static long transform_in(char c){
    if(!String.valueOf(c).matches("[a-zA-Z0-9]")){ 
        throw new RuntimeException("char should be A-Z, a-z, or 0-9, inclusive");
    }
    long num = (long) c;

    long out;
    if(num >= START_A && num <= START_A+26) out = num-START_A;
    else if(num >= START_a && num <= START_a+26) out = (num-START_a) + 26;
    else if(num >= START_0 && num <= START_0+10) out = (num-START_0) + 52;
    else throw new RuntimeException("Salah, bego!");

    return out;
}}

I've written a Ruby tool that does what you need. (Sorry it isn't Java, but my Java is over a decade old by now. But the general idea should port to Java without hassle.)

In short, the given input string (7 bytes) is compressed into a number between 0 and 175_760_000 (28 bits). A 32 bit random number is prepended, making a 60 bit integer, which is encoded into a 10-character output string.

My earlier math was wrong; since your input is only 28 bits long and your desired output is 60 bits long, that leaves 32 bits for adding roughly two billion random permutations. I mistyped when performing my calculations.

#!/usr/bin/ruby -w
START_A = "A"[0]
START_a = "a"[0]
START_0 = "0"[0]
CODEMASK = ((1 << 28) - 1) # turn on lower 28 bits
RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK # turn on upper 32 bits


def compress(line)
    a, b, c, h, i, j, k = line.chomp().each_char().entries()
    a, b, c = a[0], b[0], c[0]
    small_a, small_b, small_c = a - START_A, b - START_A, c - START_A
    letters = (small_a * 26**2) + (small_b * 26) + small_c
    h, i, j, k = Integer(h), Integer(i), Integer(j), Integer(k)
    number = letters * 10_000 + h*1000 + i*100 + j*10 + k
end

def uncompress(number)
    k = number % 10
    number /= 10
    j = number % 10
    number /= 10
    i = number % 10
    number /= 10
    h = number % 10
    number /= 10
    small_c = number % 26
    number /= 26
    small_b = number % 26
    number /= 26
    small_a = number % 26
    number /= 26
    if (number != 0)
            raise "input wasn't generated with compress()"
    end
    a, b, c = small_a + START_A, small_b + START_A, small_c + START_A
    [a.chr(), b.chr(), c.chr(), h.to_s(), i.to_s(), j.to_s(), k.to_s()].join()
end

def add_random(number)
    (rand(2**31) << 28) + number
end 

def remove_random(number)
    [number & CODEMASK, number & RANDOMMASK]
end

def to_output(number)
    a = []
    begin
            a << transform_out(number % 62)
            number /= 62
    end while(number > 0)
    a.reverse().join()
end

def from_output(string)
    num = 0
    string.each_char() do |c|
            num *= 62
            num += transform_in(c)
    end     
    num
end

def transform_out(small)
    if (small < 0 || small > 61)
            raise "small should be between 0 and 61, inclusive"
    end
    if (small < 26)
            out = START_A+small
    elsif (small < 52)
            out = START_a+(small-26)
    else
            out = START_0+(small-52)
    end
    out.chr()
end

def transform_in(char)
    if (/^[A-Za-z0-9]$/ !~ char)
            raise "char should be A-Z, a-z, or 0-9, inclusive"
    end
    num = char[0]
    out = case num
    when START_A .. START_A+26 then num - START_A
    when START_a .. START_a+26 then (num - START_a) + 26
    when START_0 .. START_0+10 then (num - START_0) + 52
    end

    out
end


begin
    while(line = DATA.readline()) do
            line.chomp!()
            c = compress(line)
            a = add_random(c)
            output = to_output(a)
            input = from_output(output)
            new_c, r = remove_random(input)
            u = uncompress(new_c)

            printf("original input: %s\n compressed: %d\n after adding random amount: %d\n *output: %s\n *input: %s\n random amount added: %d\n after removing random amount: %d\nuncompressed: %s\n", line, c, a, output, input, r, new_c, u)
    end
rescue EOFError => e
end


__END__
AAA0000
SIN1500
ABD2123
SMS3345
ZZZ9999

Running the program with the five inputs given at the bottom results in this output:

$ ./compress.rb
original input: AAA0000
 compressed: 0
 after adding random amount: 508360097408221184
 *output: liSQkzXL1G
 *input: 508360097408221184
 random amount added: 508360097408221184
 after removing random amount: 0
uncompressed: AAA0000
original input: SIN1500
 compressed: 123891500
 after adding random amount: 421470683267231532
 *output: fIVFtX9at2
 *input: 421470683267231532
 random amount added: 421470683143340032
 after removing random amount: 123891500
uncompressed: SIN1500
original input: ABD2123
 compressed: 292123
 after adding random amount: 414507907112269083
 *output: emb6JfDhUH
 *input: 414507907112269083
 random amount added: 414507907111976960
 after removing random amount: 292123
uncompressed: ABD2123
original input: SMS3345
 compressed: 124983345
 after adding random amount: 383242064398325809
 *output: cTPpccLAvn
 *input: 383242064398325809
 random amount added: 383242064273342464
 after removing random amount: 124983345
uncompressed: SMS3345
original input: ZZZ9999
 compressed: 175759999
 after adding random amount: 27149937199932031
 *output: CAVf14tiRh
 *input: 27149937199932031
 random amount added: 27149937024172032
 after removing random amount: 175759999
uncompressed: ZZZ9999

The lines you're really looking for are original input, *output, and uncompressed. Your client has the original input lines, after using the to_output(add_random(compress(input))) you will get the ten-character A-Za-z0-9 output in the *output line. You hand that to users, and it's a magic token of some sort. Then when it is time to validate them, you use remove_random(from_output(user_string)) to discover both the random value added to the string and an integer that you can hand to uncompress().

One very important note: The input AAA0000 is stored in plaintext in the lower 28 bits. The random number is stored in plaintext in the upper 32 bits. This is simply an obfuscation of the original inputs, it wouldn't be hard for someone to discover the pattern if they have two inputs and outputs. Heck, they might even correctly guess the algorithm given only a single input and output.

If you need this to be cryptographically strong, then you've still got some work ahead of you :) but that might be as easy as XORing the intermediate 60 bit number with rc4 output of the username or something like that.

Short Explanation

Your input strings can be interpreted as integers, but with a twist: the first three 'digits' are in base 26, the next four digits are in base 10. The number will be less than 175_760_000. Uniquely storing numbers between 0 and 175_760_000 requires 28 bits. Your output strings are also a number, ten digits long, with each 'digit' in base 62. (Think base64 but without -, /, or = (for padding).) 62 possible values and ten positions gives you a maximum value of 839_299_365_868_340_224, which can be represented in 60 bits.

The input string only takes 28 bits to represent, your output string has 60 bits available, and that leaves 32 bits available for storing a randomly-generated number. If we multiply a 32-bit number by 2^28 (the same as a left-shift by 28: 1 << 28) then the lower 28 bits will be free for the originally input number.

Once we've calculated the 60 bit number, we output it in our base 62 notation for human consumption.

To reverse the process, you decode the base 62 number to get our 60 bit number; split the 60 bit number into the lower 28 bit input number and upper 32 bit random number, and then output the 28 bit number in the original format: three base 26 digits followed by four base 10 digits.

FINAL UPDATE

Yusuf, excellent work converting my Ruby to Java. I'm very impressed, especially given how good your Java version looks: your version is more legible. I'm jealous and impressed. :)

I found the two small bugs that remained in your program: RANDOMMASK was accidentally initialized to 0, because Java doesn't promote all operands into the final data type when executing arithmetic shift statements. Changing 1 to 1L forces the result of 1L << 60 to be a long; without the L, the result of 1 << 60 is an int, and isn't large enough to hold the full number.

Further, the digits weren't being compressed correctly; my Ruby code parsed the characters as an integer, and your Java code interpreted the characters as an integer. (Yours used the character's value; mine converted the character to an integer based on the ascii meaning of the character. Mine wasn't really parsing, since it is just doing a subtraction, but if it were a string, String.toInteger(character) would do the same thing, so it is a lot like parsing.)

But your uncompress logic was correct, and because of the mismatch, the output was incorrect. So I changed your code to parse the digits into integers (and changed from char to int to silence a pointless warning).

Here's a diff of what I had to change in your program to make it work:

--- Compress.java.orig  2011-03-25 16:57:47.000000000 -0700
+++ Compress.java   2011-03-25 17:09:42.000000000 -0700
@@ -1,12 +1,12 @@
-import java.util.*
+import java.util.*;

 public class Compress {

 static char START_A = "A".charAt(0);
 static char START_a = "a".charAt(0);
 static char START_0 = "0".charAt(0);
-static int CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
-static int RANDOMMASK = ((1 << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits
+static long CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
+static long RANDOMMASK = ((1L << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

 public static void main(String[] args) {

@@ -42,10 +42,10 @@
     char a = line.charAt(0);
     char b = line.charAt(1);
     char c = line.charAt(2);
-    char h = line.charAt(3);
-    char i = line.charAt(4);
-    char j = line.charAt(5);
-    char k = line.charAt(6);
+    int h = line.charAt(3) - START_0;
+    int i = line.charAt(4) - START_0;
+    int j = line.charAt(5) - START_0;
+    int k = line.charAt(6) - START_0;

     long small_a = (long) a - START_A;
     long small_b = (long) b - START_A;

And now the full source, just in case that's easier :)

import java.util.*;

public class Compress {

static char START_A = "A".charAt(0);
static char START_a = "a".charAt(0);
static char START_0 = "0".charAt(0);
static long CODEMASK = ((1 << 28) - 1); //turn on lower 28 bits
static long RANDOMMASK = ((1L << 60) - 1) & ~ CODEMASK; //turn on upper 32 bits

public static void main(String[] args) {

    String[] test = new String[]{
            //"AAA0000", "SIN1500", "ABD2123", "SMS3345", "ZZZ9999",
            //"ABD2123", "ABD2123", "ABD2123", "ABD2123", "ABD2123"
            "ABD2123"
            };

    for(String t : test){
        long c = compress(t);
        long a = add_random(c);
        String output = to_output(a);
        long input = from_output(output);

        String[] new_c_r = remove_random(input);
        String u = uncompress(Long.valueOf(new_c_r[0]));

        System.out.println("Original input: " + t);
        System.out.println("    compressed: " + c);
        System.out.println("    after adding random amount: " + a);
        System.out.println("    *output: " + output);
        System.out.println("    *input: " + input);
        System.out.println("    random amount added: " + new_c_r[1]);
        System.out.println("    after removing random amount: " + new_c_r[0]);
        System.out.println("    uncompressed: " + u);
        System.out.println("-----------------------------------------------------------------");
    }

}

public static long compress(String line){ //7 character
    char a = line.charAt(0);
    char b = line.charAt(1);
    char c = line.charAt(2);
    int h = line.charAt(3) - START_0;
    int i = line.charAt(4) - START_0;
    int j = line.charAt(5) - START_0;
    int k = line.charAt(6) - START_0;

    long small_a = (long) a - START_A;
    long small_b = (long) b - START_A;
    long small_c = (long) c - START_A;
    long letters = (small_a * 26 * 26) + (small_b * 26) + small_c;
    long numbers = letters * 10000 + h * 1000 + i*100 + j*10 + k;
    return numbers;
}

public static String uncompress(long number){
    long k = number % 10;
    number /= 10;
    long j = number % 10;
    number /= 10;
    long i = number % 10;
    number /= 10;
    long h = number % 10;
    number /= 10;
    long small_c = number % 26;
    number /= 26;
    long small_b = number % 26;
    number /= 26;
    long small_a = number % 26;
    number /= 26;

    if (number != 0) throw new RuntimeException("input wasn't generated with compress()");

    long a = small_a + START_A;
    long b = small_b + START_A;
    long c = small_c + START_A;

    StringBuffer result = new StringBuffer();
    result.append((char) a).append((char) b).append((char) c).append(h).append(i).append(j).append(k);

    return result.toString();
}

public static long add_random(long number){
    return (((long) (Math.random()* Math.pow(2, 31))) << 28) + number;
}

public static String[] remove_random(long number){
    return new String[]{String.valueOf(number & CODEMASK), String.valueOf(number & RANDOMMASK)};
}

public static String to_output(long number){
    List<Character> a = new ArrayList<Character>();
    do{
        a.add(transform_out(number % 62));
        number /= 62;
    }while(number > 0);

    Collections.reverse(a);

    StringBuffer result = new StringBuffer();
    for(int i=0; i<a.size(); i++){
        Character s = (Character) a.get(i);
        result.append(s);
    }

    return result.toString();
}

public static long from_output(String string){
    long num = 0;
    for(char c : string.toCharArray()){
        num *= 62;
        num += transform_in(c);
    }
    return num;
}

public static char transform_out(long small){
    long out;

    if (small < 0 || small > 61){
        throw new RuntimeException("small should be between 0 and 61, inclusive");
    }
    if(small < 26){
        out = START_A + small;
    }else if(small < 52){
        out = START_a + (small-26);
    }else{
        out = START_0 + (small-52);
    }
    return (char) out;
}


public static long transform_in(char c){
    if(!String.valueOf(c).matches("[a-zA-Z0-9]")){ 
        throw new RuntimeException("char should be A-Z, a-z, or 0-9, inclusive");
    }
    long num = (long) c;

    long out;
    if(num >= START_A && num <= START_A+26) out = num-START_A;
    else if(num >= START_a && num <= START_a+26) out = (num-START_a) + 26;
    else if(num >= START_0 && num <= START_0+10) out = (num-START_0) + 52;
    else throw new RuntimeException("Salah, bego!");

    return out;
}}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文