IT박스

ID 난독 화

itboxs 2020. 10. 6. 08:00
반응형

ID 난독 화


정수 ID를 다른 정수로 암호화 / 난독 화하는 방법을 찾고 있습니다. 더 정확하게는 함수가 필요 int F(int x)합니다.

  • x <-> F (x)는 일대일 대응입니다 (x! = y, F (x)! = F (y)).
  • F (x)가 주어지면 x를 쉽게 찾을 수 있으므로 F는 해시 함수가 아닙니다.
  • x와 F (x)가 주어지면 F (y)를 찾는 것이 어렵거나 불가능 x ^ 0x1234합니다.

명확성을 위해 강력한 암호화 솔루션을 찾는 것이 아니라 난독 화일뿐입니다. example.com/profile/1, example.com/profile/2등과 같은 URL을 가진 웹 애플리케이션을 상상해보십시오 . 프로필 자체는 비밀이 아니지만 평범한 도촬자가 모든 프로필을 차례로 보거나 가져 오는 것을 방지하고 싶습니다. 따라서 example.com/profile/23423, example.com/profile/80980234등의 뒤에 숨기고 싶습니다 . 데이터베이스에 저장된 토큰은이 작업을 매우 쉽게 수행 할 수 있습니다. 이에 대한 간단한 수학이 있는지 궁금합니다.

내가 약을 취소하지 않은 한 가지 중요한 요구 사항은 결과, 시퀀스 제공되는 "임의"모양해야한다는 것입니다 x,x+1,...,x+n, F(x),F(x+1)...F(x+n)어떤 종류의 진행을 형성해서는 안된다.


2 개 또는 3 개의 간단한 방법을 조합하여 난독 화합니다.

  • XOR
  • 개별 비트 섞기
  • 모듈 표현으로 변환 (D.Knuth, Vol. 2, Chapter 4.3.2)
  • 32 개 (또는 64 개)의 겹치는 비트 서브 세트와 각 서브 세트에서 XOR 비트 (서브 세트의 패리티 비트)를 선택합니다.
  • 가변 길이 숫자 체계로 표현하고 숫자를 섞습니다.
  • 홀수 정수의 쌍을 선택 x하고 y, 서로 (모듈로 2 곱셈 역수임을 32 ) 후, 승산함으로써 x난독과 곱셈에 의해 y복원하기 위해서, 모든 승산은 모듈 (2)이다 (32) (출처 : 에릭 "곱셈 역수의 실용화" 리퍼 트 )

가변 길이 숫자 체계 방법은 자체적으로 "진행"요구 사항을 따르지 않습니다. 항상 짧은 산술 진행을 생성합니다. 그러나 다른 방법과 함께 사용하면 좋은 결과를 얻을 수 있습니다.

모듈 식 표현 방법도 마찬가지입니다.

다음은 이러한 메서드 중 3 가지에 대한 C ++ 코드 예제입니다. 셔플 비트 예제는 예측 불가능한 일부 다른 마스크 및 거리를 사용할 수 있습니다. 다른 두 가지 예는 작은 숫자에 적합합니다 (아이디어를 제공하기 위해). 모든 정수 값을 적절하게 난독 화하려면 확장해야합니다.

// *** Numberic system base: (4, 3, 5) -> (5, 3, 4)
// In real life all the bases multiplied should be near 2^32
unsigned y = x/15 + ((x/5)%3)*4 + (x%5)*12; // obfuscate
unsigned z = y/12 + ((y/4)%3)*5 + (y%4)*15; // restore

// *** Shuffle bits (method used here is described in D.Knuth's vol.4a chapter 7.1.3)
const unsigned mask1 = 0x00550055; const unsigned d1 = 7;
const unsigned mask2 = 0x0000cccc; const unsigned d2 = 14;

// Obfuscate
unsigned t = (x ^ (x >> d1)) & mask1;
unsigned u = x ^ t ^ (t << d1);
t = (u ^ (u  >> d2)) & mask2;
y = u ^ t ^ (t << d2);

// Restore
t = (y ^ (y >> d2)) & mask2;
u = y ^ t ^ (t << d2);
t = (u ^ (u >> d1)) & mask1;
z = u ^ t ^ (t << d1);

// *** Subset parity
t = (x ^ (x >> 1)) & 0x44444444;
u = (x ^ (x << 2)) & 0xcccccccc;
y = ((x & 0x88888888) >> 3) | (t >> 1) | u; // obfuscate

t = ((y & 0x11111111) << 3) | (((y & 0x11111111) << 2) ^ ((y & 0x22222222) << 1));
z = t | ((t >> 2) ^ ((y >> 2) & 0x33333333)); // restore

변환이 되돌릴 수 있고 명확하지 않기를 원합니다. 이는 주어진 범위에서 숫자를 가져와 동일한 범위에서 다른 숫자를 생성하는 암호화처럼 들립니다. 범위가 64 비트 숫자이면 DES를 사용하십시오. 범위가 128 비트 숫자이면 AES를 사용하십시오. 다른 범위를 원할 경우 가장 좋은 방법은 100,000에서 999,999와 같이 블록에 깔끔하게 맞지 않는 여러 블록 크기 및 숫자 범위에 대처하도록 설계된 Hasty Pudding cipher 일 것입니다.


난독 화는 보안 측면에서 실제로 충분하지 않습니다.

그러나 캐주얼 구경꾼을 방해하려는 경우 두 가지 방법을 조합하는 것이 좋습니다.

  • 함께 xor'ing하여 id와 결합하는 개인 키
  • 키 적용 전후에 일정한 양만큼 비트 회전

다음은 의사 코드를 사용하는 예입니다.

  def F(x)
    x = x XOR 31415927       # XOR x with a secret key
    x = rotl(x, 5)           # rotate the bits left 5 times
    x = x XOR 31415927       # XOR x with a secret key again
    x = rotr(x, 5)           # rotate the bits right 5 times
    x = x XOR 31415927       # XOR x with a secret key again
    return x                 # return the value
  end

나는 그것을 테스트하지 않았지만 이것은 되돌릴 수 있고 빠르며 방법을 알아내는 것이 너무 쉽지 않다고 생각합니다.


이 특정 Python / PHP 코드가 매우 유용하다는 것을 알았습니다.

https://github.com/marekweb/opaque-id


나는 블록 암호를 사용한 안전한 순열 에 대한 기사를 썼는데 , 이는 명시된대로 요구 사항을 충족해야합니다.

하지만 추측하기 어려운 식별자를 원할 경우 처음에 식별자를 사용하는 것이 좋습니다. UUID를 생성하고 처음에는이를 레코드의 기본 키로 사용하십시오. '실제'ID로 /에서 변환합니다.


그들을 파괴하지 않을 ID의 비트로 무엇이든하십시오. 예를 들면 :

  • 값을 회전
  • 검색을 사용하여 값의 특정 부분을 바꿉니다.
  • 어떤 가치를 가진 xor
  • 스왑 비트
  • 스왑 바이트
  • 전체 가치를 반영
  • 가치의 일부를 미러링
  • ... 너의 상상력을 사용해라

암호 해독의 경우 모든 작업을 역순으로 수행하십시오.

흥미로운 몇 가지 값을 '암호화'하는 프로그램을 만들어 검사 할 수있는 테이블에 넣습니다. 동일한 프로그램이 시스템에 포함하려는 모든 값 세트로 암호화 / 복호화 루틴을 테스트하도록하십시오.

당신의 숫자가 당신에게 적절하게 엉망이 될 때까지 위의 목록에있는 것들을 루틴에 추가하십시오.

그 밖의 사항은 The Book을 받으십시오 .


이 스레드의 일부 아이디어를 사용하여 JS 코드를 작성했습니다.

const BITS = 32n;
const MAX = 4294967295n;
const COPRIME = 65521n;
const INVERSE = 2166657316n;
const ROT = 6n;
const XOR1 = 10296065n; 
const XOR2 = 2426476569n;


function rotRight(n, bits, size) {
    const mask = (1n << bits) - 1n;
    // console.log('mask',mask.toString(2).padStart(Number(size),'0'));
    const left = n & mask;
    const right = n >> bits;
    return (left << (size - bits)) | right;
}

const pipe = fns => fns.reduce((f, g) => (...args) => g(f(...args)));

function build(...fns) {
    const enc = fns.map(f => Array.isArray(f) ? f[0] : f);
    const dec = fns.map(f => Array.isArray(f) ? f[1] : f).reverse();

    return [
        pipe(enc),
        pipe(dec),
    ]
}

[exports.encode, exports.decode] = build(
    [BigInt, Number],
    [i => (i * COPRIME) % MAX, i => (i * INVERSE) % MAX],
    x => x ^ XOR1,
    [x => rotRight(x, ROT, BITS), x => rotRight(x, BITS-ROT, BITS)],
    x => x ^ XOR2,
);

다음과 같은 멋진 결과가 생성됩니다.

1 1352888202n 1 'mdh37u'
2 480471946n 2 '7y26iy'
3 3634587530n 3 '1o3xtoq'
4 2225300362n 4 '10svwqy'
5 1084456843n 5 'hxno97'
6 212040587n 6 '3i8rkb'
7 3366156171n 7 '1jo4eq3'
8 3030610827n 8 '1e4cia3'
9 1889750920n 9 'v93x54'
10 1017334664n 10 'gtp0g8'
11 4171450248n 11 '1wzknm0'
12 2762163080n 12 '19oiqo8'
13 1621319561n 13 'qtai6h'
14 748903305n 14 'cdvlhl'
15 3903018889n 15 '1sjr8nd'
16 3567473545n 16 '1mzzc7d'
17 2426613641n 17 '144qr2h'
18 1554197390n 18 'ppbudq'
19 413345678n 19 '6u3fke'
20 3299025806n 20 '1ik5klq'
21 2158182286n 21 'zoxc3y'
22 1285766031n 22 'l9iff3'
23 144914319n 23 '2ea0lr'
24 4104336271n 24 '1vvm64v'
25 2963476367n 25 '1d0dkzz'
26 2091060108n 26 'ykyob0'
27 950208396n 27 'fpq9ho'
28 3835888524n 28 '1rfsej0'
29 2695045004n 29 '18kk618'
30 1822628749n 30 'u559cd'
31 681777037n 31 'b9wuj1'
32 346231693n 32 '5q4y31'

테스트 대상 :

  const {encode,decode} = require('./obfuscate')

  for(let i = 1; i <= 1000; ++i) {
        const j = encode(i);
        const k = decode(j);
        console.log(i, j, k, j.toString(36));
   }

XOR1XOR20 사이의 무작위 숫자입니다 MAX. MAX이다 2**32-1; 가장 높은 ID가 될 것이라고 생각하는 값으로 설정해야합니다.

COPRIME is a number that's coprime w/ MAX. I think prime numbers themselves are coprime with every other number (except multiples of themselves).

INVERSE is the tricky one to figure out. These blog posts don't give a straight answer, but WolframAlpha can figure it out for you. Basically, just solve the equation (COPRIME * x) % MAX = 1 for x.

The build function is something I created to make it easier to create these encode/decode pipelines. You can feed it as many operations as you want as [encode, decode] pairs. These functions have to be equal and opposite. The XOR functions are their own compliments so you don't need a pair there.


Here's another fun involution:

function mixHalves(n) {
    const mask = 2n**12n-1n;
    const right = n & mask;
    const left = n >> 12n;
    const mix = left ^ right;
    return (mix << 12n) | right;
}

(assumes 24-bit integers -- just change the numbers for any other size)


Not sure how "hard" you need it to be, how fast, or how little memory to use. If you have no memory constraints you could make a list of all integers, shuffle them and use that list as a mapping. However, even for a 4 byte integer you would need a lot of memory.

However, this could be made smaller so instead of mapping all integers you would map only 2 (or worst case 1) byte and apply this to each group in the integer. So, using 2 bytes a integer would be (group1)(group2) you would map each group through the random map. But that means that if you only change group2 then the mapping for group1 would stay the same. This could "fixed" by mapping different bits to each group.

So, *(group2) could be (bit 14,12,10,8,6,4,2,0) so, adding 1 would change both group1 and group2.

Still, this is only security by obscurity, anyone that can feed numbers into your function (even if you keep the function secret) could fairly easily figure it out.


Generate a private symmetric key for use in your application, and encrypt your integer with it. This will satisfy all three requirements, including the hardest #3: one would need to guess your key in order to break your scheme.


What you're describing here seems to be the opposite of a one-way function: it's easy to invert but super difficult to apply. One option would be to use a standard, off-the-shelf public-key encryption algorithm where you fix a (secret, randomly-chosen) public key that you keep a secret and a private key that you share with the world. That way, your function F(x) would be the encryption of x using the public key. You could then easily decrypt F(x) back to x by using the private decryption key. Notice that the roles of the public and private key are reversed here - you give out the private key to everyone so that they can decrypt the function, but keep the public key secret on your server. That way:

  1. The function is a bijection, so it's invertible.
  2. Given F(x), x is efficiently computable.
  3. Given x and F(x), it is extremely difficult to compute F(y) from y, since without the public key (assuming you use a cryptographically strong encryption scheme) there is no feasible way to encrypt the data, even if the private decryption key is known.

This has many advantages. First, you can rest assured that the crypto system is safe, since if you use a well-established algorithm like RSA then you don't need to worry about accidental insecurity. Second, there are already libraries out there to do this, so you don't need to code much up and can be immune to side-channel attacks. Finally, you can make it possible for anyone to go and invert F(x) without anyone actually being able to compute F(x).

One detail- you should definitely not just be using the standard int type here. Even with 64-bit integers, there are so few combinations possible that an attacker could just brute-force try inverting everything until they find the encryption F(y) for some y even if they don't have the key. I would suggest using something like a 512-bit value, since even a science fiction attack would not be able to brute-force this.

Hope this helps!


If xor is acceptable for everything but inferring F(y) given x and F(x) then I think you can do that with a salt. First choose a secret one-way function. For example S(s) = MD5(secret ^ s). Then F(x) = (s, S(s) ^ x) where s is chosen randomly. I wrote that as a tuple but you can combine the two parts into an integer, e.g. F(x) = 10000 * s + S(s) ^ x. The decryption extracts the salt s again and uses F'(F(x)) = S(extract s) ^ (extract S(s)^x). Given x and F(x) you can see s (though it is slightly obfuscated) and you can infer S(s) but for some other user y with a different random salt t the user knowing F(x) can't find S(t).

참고URL : https://stackoverflow.com/questions/8554286/obfuscating-an-id

반응형