Невідомість посвідчення особи


85

Я шукаю спосіб шифрування / забруднення цілочисельного ідентифікатора в інше ціле число. Точніше, мені потрібна функція int F(int x), так що

  • x <-> F (x) - відповідність один до одного (якщо x! = y, F (x)! = F (y))
  • враховуючи F (x), легко з’ясувати x - отже, F не є хеш-функцією
  • з огляду на x та F (x) важко / неможливо з’ясувати F (y), щось на зразок x ^ 0x1234не буде працювати

Для наочності я не шукаю надійного рішення шифрування, це лише затуманення. Уявіть собі веб - додаток з URL - адрес , як example.com/profile/1, і example.com/profile/2т.д. Профілі самі не є секретом, але я хотів би, щоб запобігти випадковим вуаеристів для перегляду / вилучення всіх профілів один за іншим, так що я вважав за краще б приховати їх за що - щось на зразок example.com/profile/23423, і example.com/profile/80980234т.д. Хоча токени, що зберігаються в базі даних, можуть зробити цю роботу досить легко, мені цікаво, чи є для цього доступна якась проста математика.

Однією з важливих вимог, з якою я не був зрозумілий, є те, що результати повинні виглядати "випадковими", тобто, даючи послідовність x,x+1,...,x+n, F(x),F(x+1)...F(x+n)не повинні утворювати будь-якого прогресу.


Int F (int x) - це вимога, чи це може бути int [2] F (int x)?
Eugen Rieck

@ Eugen Rieck, в ідеалі, я хотів би, щоб x і F (x) знаходились у діапазоні чисел
georg

@ toon81, так, функція буде триматися в секреті
georg

оскільки ви сказали, що хотіли б піти без маркера, чи означає це, що ви хочете уникати будь-якої таблиці пошуку?
Даніель Мошмондор,

16
Чоловіче, це питання чітко сформульоване і саме те, що я шукаю. Хороша робота.
Снексе

Відповіді:


39

Затуманіть його за допомогою комбінації 2 або 3 простих методів:

  • XOR
  • перетасувати окремі біти
  • перетворити на модульне представлення (Д. Кнут, том 2, глава 4.3.2)
  • вибрати 32 (або 64) перекриваються підмножини бітів та біти XOR у кожному підмножині (біти парності підмножин)
  • представляють його в числовій системі змінної довжини та цифрах у порядку перетасування
  • вибрати пару непарних чисел , xі yякі є мультиплікативними обернений один до одного ( по модулю- 32 ), потім помножити на xзаплутати і помножити на yвідновлення, все множення по модулю- 32 (джерело: «Практичне застосування мультиплікативний зворотних» Ерік Ліпперт )

Числовий метод із змінною довжиною самостійно не відповідає вашим вимогам щодо "прогресування". Він завжди виробляє короткі арифметичні прогресії. Але в поєднанні з якимсь іншим методом це дає хороші результати.

Те саме стосується методу модульного представлення.

Ось приклад коду на C ++ для 3 з цих методів. Приклад перемішування бітів може використовувати різні маски та відстані, щоб бути більш непередбачуваними. Інші 2 приклади хороші для невеликих цифр (лише для того, щоб дати ідею). Їх слід розширити, щоб правильно затушувати всі цілі значення.

// *** 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

Спасибі за вашу відповідь. Якщо б ви могли навести кілька прикладів псевдокоду, це було б чудово.
georg

3
@ thg435 Я використовував C ++ замість псевдокоду. Не хотів наводити неперевірених прикладів.
Євген Клюєв

1
Коли я пробую цифровий системний базовий код вище з x = 99, я отримую z = 44.
Харві

@Harvey: щоб отримати оборотний обфускатор, добуток усіх баз повинен бути більшим за число, щоб затушувати. У цьому прикладі 3 * 4 * 5 = 60, тому будь-яке більше число (наприклад, 99) не обов'язково буде відновлено до того самого значення.
Євген Клуєв,

1
@Harvey: Також можна отримати добуток усіх баз, менший, але дуже близький до 2 ^ 32, а потім затушувати решту значень за допомогою невеликої таблиці. У цьому випадку все залишається у 32-розрядних числах.
Євген Клуєв

8

Ви хочете, щоб трансформація була оборотною і не очевидною. Це звучить як шифрування, яке приймає число в заданому діапазоні і створює інше число в тому ж діапазоні. Якщо ваш діапазон становить 64 бітові числа, тоді використовуйте DES. Якщо ваш діапазон становить 128 бітових чисел, використовуйте AES. Якщо ви хочете мати інший діапазон, то найкращим варіантом є, мабуть, шифр поспішного пудингу , який призначений для роботи з різними розмірами блоків та діапазонами чисел, які не вкладаються в блок акуратно, наприклад, від 100 000 до 999 999.


Цікаві речі, але може бути трохи складно попросити когось застосувати шифр, який 1) не був добре протестований і 2) не був протестований добре, тому що це так важко зрозуміти :)
Маартен Бодеус

Дякую! Хоча я намагаюся зробити це якомога простішим.
georg

Якщо ви не можете знайти реалізацію поспішного пудингу (вам потрібен лише один із дозволених розмірів), тоді ви можете легко застосувати простий 4-раундовий шифр Фейстеля ( en.wikipedia.org/wiki/Feistel_cipher ) у парному розмірі блоку. Просто продовжуйте шифрувати, доки вихід не буде в правильному діапазоні, як у поспішному пудингу. Не безпечно, але достатньо, щоб затуманити.
rossum

Зараз NSA випустила шифр Speck, який включає версії, що включають 32-бітні та 48-бітні розміри блоків. Це також може бути корисно для забруднення чисел з такими розмірами. Зокрема, 32-розрядна версія може бути корисною.
россум

5

Заплутаність насправді недостатня з точки зору безпеки.

Однак, якщо ви намагаєтеся перешкодити випадковому спостерігачеві, я рекомендую поєднати два методи:

  • Приватний ключ, який ви поєднуєте з ідентифікатором, поєднуючи їх разом
  • Поворот бітів на певну величину як до, так і після застосування ключа

Ось приклад (з використанням псевдокоду):

  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

Я не тестував його, але я думаю, що це оборотно, повинно бути швидким і не надто простим для роздратування методу.


Також є додаток постійного мода 2 ^ 32 (оскільки ваше обертання бітів нагадувало мені про rot13, улюблену усім тривіально оборотну функцію).
ccoakley

Це насправді просто return x XOR rotr(31415927, 5), правда? Останній xor скасовує перший, а обертається скасовує один одного .. звичайно, будь-який ланцюг оборотних операцій також є оборотним, тому він задовольняє цій умові.
harold

Я провів кілька коротких тестів і переконаний, що результати такі, як очікувалось. Як зазначає ccoakley, rot13 можна використовувати замість rot5, будь-яке обертання буде працювати (застереження: 0> rot> цілочисельний розмір) і може вважатися іншим ключем. Є й інші речі, які ви можете сюди закинути, наприклад модуль, як він припускає, і доки вони оборотні, як згадував Гарольд.
IAmNaN

1
Вибачте, але @harold в основному правильний - вся ваша функція еквівалентна x = x XOR F(0), або x = x XOR 3087989491, або x = x XOR rotr(31415927, 5). Ваш перший і останній xors заперечують один одного, тому все, що ви робите, - це xoring біт-зміщеного вводу за допомогою ключа - або еквівалентно, xoring вводу за допомогою біт-зміщеного ключа. Зауважте, що це вірно, навіть якщо ви використовували різні клавіші для кожного етапу - усі ключі могли бути скомпоновані в єдиний ключ, який можна змішати з відкритим текстом.
Нік Джонсон,

2
Це ще гірше, досить легко довести, що будь-який ланцюг обертається з постійним зміщенням, а xors з постійною можуть конденсуватися лише до одного обертання та лише до одного xor. Два обертання після кожного можуть поєднуватися (додати їх зміщення), два xors після кожного можуть поєднуватися (xor з xor двох констант), а пара xor / rot може бути замінена на rot / xor, застосовуючи те саме обертання до константа в xor.
harold


3

Я написав деякий код 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));
   }

XOR1і XOR2є просто випадковими числами від 0 до MAX. MAXє 2**32-1; ви повинні встановити для цього будь-яке, на вашу думку, ваше найвище посвідчення особи.

COPRIME- це число, яке є спільним із w MAX. Я думаю, що самі прості числа співпадають з кожним іншим числом (крім кратних їм самих).

INVERSEце складно зрозуміти. Ці повідомлення в блозі не дають прямої відповіді, але WolframAlpha може це зрозуміти для вас . В основному, просто розв’яжіть рівняння (COPRIME * x) % MAX = 1для x.

buildФункція що - то я створив , щоб зробити його простіше для створення цих кодування / декодування трубопроводів. Ви можете годувати його скільки завгодно операцій[encode, decode] парах. Ці функції повинні бути рівними та протилежними. Ці XORфункції є їх власними компліментами , так що вам не потрібна там пара.


Ось ще одна весела інволюція :

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

(передбачає 24-бітові цілі числа - просто змініть цифри на будь-який інший розмір)


1
класно, дякую за обмін! До речі, що таке "32n"? Ніколи раніше цього не бачив.
georg

1
n- це постфікс номера для BigInts . Це нова функція JS, яка дозволяє обробляти справді великі числа. Мені потрібно було його використовувати, оскільки я множу на дійсно великі числа, які можуть призвести до тимчасового перевищення Number.MAX_SAFE_INTEGERта втрати точності одного з проміжних значень .
mpen

2

Робіть що-небудь із бітами посвідчення особи, що не призведе до їх знищення. Наприклад:

  • повернути значення
  • використовувати пошук для заміни певних частин значення
  • xor з деяким значенням
  • поміняти біти
  • підміняти байти
  • дзеркально відображати ціле значення
  • дзеркально відображати частину вартості
  • ... використовуйте свою уяву

Для розшифровки робіть все це у зворотному порядку.

Створіть програму, яка «зашифрує» для вас деякі цікаві значення та помістить їх у таблицю, яку ви зможете перевірити. Нехай одна програма перевіряє вашу процедуру шифрування / дешифрування з усім набором значень, які ви хочете мати у своїй системі.

Додавайте речі до вищезазначеного списку до процедур, поки ваші номери не будуть виглядати правильно для вас.

За будь-чим іншим отримайте примірник книги .


Те, що ви описуєте, - це будівельні блоки блок-шифру. Більше сенсу використовувати вже існуючий, ніж вигадувати власний.
Нік Джонсон,

@NickJohnson Я знаю це, ти натиснув посилання в останньому рядку мого допису?
Даніель Мошмондор,

Мені не вдалося скласти комбінацію rotl / xor, що дає результати, які виглядають досить "випадковими" (див. Оновлення). Будь-які вказівники?
georg

@ DanielMošmondor Я знаю, з чим ви пов'язуєте - але це не змінює той факт, що ви спочатку пропонуєте йому щось будувати самостійно, коли набагато більше сенсу просто використовувати вже існуючий?
Нік Джонсон,

@NickJohnson, очевидно, OP не хоче використовувати існуючу криптографію, оскільки він або хоче вчитися, або не вивчати нові API. Я цілком можу до цього ставитися.
Даніель Мошмондор,

2

Я написав статтю про безпечні перестановки з блоковими шифрами , які повинні відповідати вашим вимогам, як зазначено.

Однак я б запропонував, що якщо ви хочете важко вгадати ідентифікатори, вам слід просто використовувати їх в першу чергу: генерувати UUID і використовувати їх як первинний ключ для ваших записів, перш за все - немає необхідності мати можливість для перетворення на і з "справжнього" ідентифікатора.


2
@ thg435 Якщо вас цікавить такий підхід, корисним пошуковим терміном є "Шифрування, що зберігає формат". Сторінка wikipedia охоплює статтю Black / Rogaway, згадану в статті Ніка, а також останні події. Я успішно використовував FPE для чогось подібного до того, що ви робите; хоча в моєму випадку я додав кілька бітів крім ідентифікатора, який використовував для легкої перевірки дійсності.
Paul Du Bois

1

Не впевнений, наскільки "жорстким" він вам повинен бути, наскільки швидким або скільки пам'яті використовувати. Якщо у вас немає обмежень пам'яті, ви можете скласти список усіх цілих чисел, перемішати їх і використовувати цей список як відображення. Однак навіть для 4-байтового цілого числа вам знадобиться багато пам'яті.

Однак це можна зробити меншим, тому замість відображення всіх цілих чисел ви зіставите лише 2 (або найгірший випадок 1) байт і застосуєте це до кожної групи у цілому числу. Отже, використовуючи 2 байти, цілим числом буде (group1) (group2), ви зіставите кожну групу через випадкову карту. Але це означає, що якщо ви лише зміните group2, то відображення для group1 залишиться незмінним. Це можна "виправити", зіставивши різні біти для кожної групи.

Отже, * (group2) може бути (bit 14,12,10,8,6,4,2,0), отже, додавання 1 змінить і group1, і group2 .

Тим не менш, це лише безпека через неясність, кожен, хто може вводити цифри у вашу функцію (навіть якщо ви зберігаєте функцію в секреті), може досить легко це зрозуміти.


Залежно від обмежень системи, це, мабуть, не спрацює, тому що якщо ви можете перетворити F (x) назад на x, то вам доведеться мати доступну перестановку, з якої ви могли б легко обчислити F (y) для будь-якого довільний y.
templatetypedef

@templatetypedef Як я вже говорив, це лише безпека через неясність. Перестановка повинна була б бути відома, але ви могли б бачити перестановку як "ключ". Найбільша проблема тут полягає в тому, що ОП, схоже, хоче мати можливість шифрувати всі повідомлення в одному наборі (маленькому), де зашифроване повідомлення повинно вміщуватися в одному наборі, і це повинно бути дійсним для всіх повідомлень у наборі.
Роджер Ліндсьє,

Дякую. Я намагаюся уникати будь-яких таблиць пошуку.
georg

1

Створіть приватний симетричний ключ для використання у вашому додатку та зашифруйте ним ціле число. Це задовольнить усі три вимоги, включаючи найскладнішу No3: потрібно буде вгадати ваш ключ, щоб зламати вашу схему.


thg435 запитував ціле до цілого числа (і, наскільки я розумію, це повинно працювати для всіх цілих чисел). Чи можете ви запропонувати алгоритм приватного ключа, який би мав ці властивості?
Роджер Ліндсьє,

1

Те, що ви описуєте тут, здається протилежністю односторонньої функції: її легко інвертувати, але надзвичайно складно застосувати. Одним із варіантів було б використовувати стандартний, готовий до використання алгоритм шифрування відкритих ключів, де ви фіксуєте (секретний, випадково обраний) відкритий ключ, який ви зберігаєте в секреті, і приватний ключ, яким ви ділитесь зі світом. Таким чином, вашою функцією F (x) буде шифрування x за допомогою відкритого ключа. Потім ви можете легко розшифрувати F (x) назад до x за допомогою приватного ключа розшифровки. Зверніть увагу, що ролі відкритого та закритого ключів тут змінені - ви видаєте приватний ключ усім, щоб вони могли розшифрувати функцію, але зберігаєте відкритий ключ у секреті на своєму сервері. Цей шлях:

  1. Функція є бієкцією, тому вона оборотна.
  2. Враховуючи F (x), x ефективно обчислюється.
  3. Враховуючи x та F (x), надзвичайно складно обчислити F (y) з y, оскільки без відкритого ключа (якщо ви використовуєте криптографічно надійну схему шифрування) немає жодного можливого способу шифрування даних, навіть якщо приватний ключ розшифровки відомий.

Це має багато переваг. По-перше, ви можете бути впевнені, що криптосистема безпечна, оскільки якщо ви використовуєте добре усталений алгоритм, такий як RSA, то вам не потрібно турбуватися про випадкову незахищеність. По-друге, для цього вже є бібліотеки, тому вам не потрібно багато кодувати, і ви можете бути несприйнятливі до атак бічних каналів. Нарешті, ви можете зробити так, щоб кожен міг перейти і інвертувати F (x), не маючи змоги обчислити F (x).

Одна деталь - вам точно не слід використовувати тут просто стандартний тип int. Навіть із 64-розрядними цілими числами існує настільки мало комбінацій, що зловмисник може просто грубою силою спробувати перетворити все, поки не знайде шифрування F (y) для деякого y, навіть якщо у них немає ключа. Я б запропонував використати щось на зразок 512-бітового значення, оскільки навіть атака наукової фантастики не змогла б застосувати це.

Сподіваюся, це допомагає!


Але, схоже, thg435 просить шифрування, яке може зашифрувати невеликий набір повідомлень (4 байтові повідомлення) в один і той же набір повідомлень, і шифрування повинно працювати для всіх повідомлень.
Роджер Ліндсьє,

Спасибі за вашу відповідь. Використання повномасштабної системи шифрування - це, мабуть, найкращий спосіб зробити це, але трохи занадто "важкий" для моїх потреб.
georg

1

Якщо xorце прийнятно для всього, крім висновку, F(y)даного, xі F(x)тоді я думаю, що ви можете зробити це за допомогою солі . Спочатку виберіть секретну односторонню функцію. Наприклад S(s) = MD5(secret ^ s). Тоді F(x) = (s, S(s) ^ x)де sобрано випадковим чином. Я писав це як кортеж, але ви можете поєднати ці дві частини у ціле число, наприклад F(x) = 10000 * s + S(s) ^ x. Розшифровка sзнову витягує сіль і використовує F'(F(x)) = S(extract s) ^ (extract S(s)^x). Дано, xі F(x)ви можете бачити s(хоча це трохи затуманено), і ви можете зробити висновок, S(s)але для іншого користувача yз іншою випадковою сіллю, який tкористувач знає, F(x)не може знайти S(t).


Дякую, але це не виглядає для мене досить випадковим (див. Оновлення)
georg

Сіль вибирається випадковим чином, а хеш S(s)також буде виглядати випадковим, тому F(x)взагалі не буде прогресувати.
Бен Джексон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.