Je cherche un moyen de crypter/obscurcir un ID entier en un autre entier. Plus précisément, j'ai besoin d'une fonction int F(int x)
, pour que
x ^ 0x1234
ne fonctionnera pasPour plus de clarté, je ne recherche pas une solution de cryptage solide, c'est seulement de l'obscurcissement. Imaginez une application Web avec des URL comme example.com/profile/1
, example.com/profile/2
Etc. Les profils eux-mêmes ne sont pas secrets, mais j'aimerais empêcher les voyeurs occasionnels de voir/récupérer tous les profils les uns après les autres, donc je Je préfère les cacher derrière quelque chose comme example.com/profile/23423
, example.com/profile/80980234
etc. Bien que les jetons stockés dans la base de données puissent faire le travail assez facilement, je suis curieux de savoir si des calculs simples sont disponibles pour cela.
Une exigence importante sur laquelle je n'étais pas clair est que les résultats doivent être "aléatoires", c'est-à-dire, étant donné une séquence x,x+1,...,x+n
, F(x),F(x+1)...F(x+n)
ne doit pas former une progression de quelque sorte que ce soit.
Obscurcissez-le avec une combinaison de 2 ou 3 méthodes simples:
x
et y
qui sont des inverses multiplicatifs l'un de l'autre (modulo 232), puis multipliez par x
pour masquer et multipliez par y
pour restaurer, toutes les multiplications sont modulo 232 (source: "Une utilisation pratique des inverses multiplicatifs" par Eric Lippert )La méthode du système numérique à longueur variable n'obéit pas à elle seule à votre exigence de "progression". Elle produit toujours de courtes progressions arithmétiques. Mais lorsqu'il est combiné avec une autre méthode, il donne de bons résultats.
Il en va de même pour la méthode de représentation modulaire.
Voici un exemple de code C++ pour 3 de ces méthodes. Exemple de bits de mélange peut utiliser différents masques et distances pour être plus imprévisible. Les 2 autres exemples sont bons pour les petits nombres (juste pour donner l'idée). Ils doivent être étendus pour masquer correctement toutes les valeurs entières.
// *** 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
Vous voulez que la transformation soit réversible et non évidente. Cela ressemble à un cryptage qui prend un nombre dans une plage donnée et produit un nombre différent dans la même plage. Si votre plage est composée de 64 bits, utilisez DES. Si votre plage est de 128 bits, utilisez AES. Si vous voulez une plage différente, alors votre meilleur pari est probablement Chiffre Hasty Pudding , qui est conçu pour faire face à différentes tailles de blocs et à des plages de nombres qui ne rentrent pas parfaitement dans un bloc, comme 100 000 à 999,999.
L'obfuscation n'est pas vraiment suffisante en termes de sécurité.
Cependant, si vous essayez de contrecarrer le spectateur occasionnel, je recommanderais une combinaison de deux méthodes:
Voici un exemple (en utilisant un pseudo code):
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
Je ne l'ai pas testé, mais je pense que cela est réversible, devrait être rapide et pas trop facile pour démêler la méthode.
J'ai trouvé ce morceau particulier de code Python/PHP très utile:
J'ai écrit du code JS en utilisant certaines des idées de ce fil:
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,
);
Il produit quelques bons résultats comme:
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'
Test avec:
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
et XOR2
ne sont que des nombres aléatoires compris entre 0 et MAX
. MAX
est 2**32-1
; vous devez définir ce paramètre comme vous pensez que votre ID le plus élevé sera.
COPRIME
est un nombre coprime avec MAX
. Je pense que les nombres premiers eux-mêmes sont des nombres premiers avec tous les autres nombres (sauf les multiples d'eux-mêmes).
INVERSE
est le plus difficile à comprendre. Ces articles de blog ne donnent pas de réponse directe, mais WolframAlpha peut le comprendre pour vous . Fondamentalement, il suffit de résoudre l'équation (COPRIME * x) % MAX = 1
pour x
.
La fonction build
est quelque chose que j'ai créé pour faciliter la création de ces pipelines d'encodage/décodage. Vous pouvez l'alimenter autant d'opérations que vous le souhaitez comme [encode, decode]
paires. Ces fonctions doivent être égales et opposées. Les fonctions XOR
sont leurs propres compliments, vous n'avez donc pas besoin d'une paire.
Voici un autre plaisir 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;
}
(suppose des entiers 24 bits - changez simplement les nombres pour toute autre taille)
Faites quoi que ce soit avec les bits de l'ID qui ne les détruira pas. Par exemple:
Pour le déchiffrement, faites tout cela dans l'ordre inverse.
Créez un programme qui "chiffrera" certaines valeurs intéressantes pour vous et les mettre dans un tableau que vous pouvez examiner. Demandez au même programme de TESTER votre routine de chiffrement/déchiffrement AVEC tous les ensembles de valeurs que vous souhaitez avoir dans votre système.
Ajoutez des éléments à la liste ci-dessus dans les routines jusqu'à ce que vos numéros vous semblent correctement déformés.
Pour toute autre chose, obtenez une copie de The Book .
J'ai écrit un article sur permutations sécurisées avec chiffrement par blocs , qui devrait répondre à vos besoins comme indiqué.
Je suggérerais, cependant, que si vous voulez des identificateurs difficiles à deviner, vous devriez simplement les utiliser en premier lieu: générer des UUID, et les utiliser comme clé primaire pour vos enregistrements en premier lieu - il n'est pas nécessaire de pouvoir pour convertir vers et depuis un "vrai" ID.
Si xor
est acceptable pour tout sauf en déduisant F(y)
étant donné x
et F(x)
alors je pense que vous pouvez le faire avec un sel. Choisissez d'abord une fonction unidirectionnelle secrète. Par exemple S(s) = MD5(secret ^ s)
. Puis F(x) = (s, S(s) ^ x)
où s
est choisi au hasard. J'ai écrit cela en tant que Tuple mais vous pouvez combiner les deux parties en un entier, par exemple F(x) = 10000 * s + S(s) ^ x
. Le déchiffrement extrait à nouveau le sel s
et utilise F'(F(x)) = S(extract s) ^ (extract S(s)^x)
. Étant donné x
et F(x)
vous pouvez voir s
(bien qu'il soit légèrement obscurci) et vous pouvez déduire S(s)
mais pour un autre utilisateur y
avec un sel aléatoire différent t
l'utilisateur connaissant F(x)
ne peut pas trouver S(t)
.
Vous ne savez pas à quel point vous avez besoin d'être "dur", à quelle vitesse ou avec peu de mémoire à utiliser. Si vous n'avez pas de contraintes de mémoire, vous pouvez faire une liste de tous les entiers, les mélanger et utiliser cette liste comme mappage. Cependant, même pour un entier de 4 octets, vous auriez besoin de beaucoup de mémoire.
Cependant, cela pourrait être plus petit, donc au lieu de mapper tous les entiers, vous ne mapperiez que 2 octets (ou le pire des cas 1) et l'appliqueriez à chaque groupe de l'entier. Donc, en utilisant 2 octets, un entier serait (groupe1) (groupe2) vous mapperiez chaque groupe à travers la carte aléatoire. Mais cela signifie que si vous modifiez uniquement le groupe2, le mappage du groupe1 restera le même. Cela pourrait être "corrigé" en mappant différents bits à chaque groupe.
Donc, * (groupe2) pourrait être (bit 14,12,10,8,6,4,2,0) donc, ajouter 1 changerait à la fois groupe1 et groupe2 .
Pourtant, ce n'est que la sécurité par l'obscurité, toute personne qui peut introduire des chiffres dans votre fonction (même si vous gardez la fonction secrète) pourrait assez facilement le comprendre.
Ce que vous décrivez ici semble être l'opposé d'une fonction à sens unique: il est facile à inverser mais super difficile à appliquer. Une option consisterait à utiliser un algorithme de cryptage à clé publique standard, standard, dans lequel vous corrigez une clé publique (secrète, choisie au hasard) que vous gardez secrète et une clé privée que vous partagez avec le monde. De cette façon, votre fonction F(x) serait le cryptage de x à l'aide de la clé publique. Vous pourriez alors facilement décrypter F(x) retour à x en utilisant la clé de déchiffrement privée. Notez que les rôles de la clé publique et privée sont inversés ici - vous donnez la clé privée à tout le monde afin qu'ils puissent déchiffrer la fonction, mais garder la clé publique secrète sur votre serveur. De cette façon:
Cela présente de nombreux avantages. Tout d'abord, vous pouvez être assuré que le système de chiffrement est sûr, car si vous utilisez un algorithme bien établi comme RSA, vous n'avez pas à vous soucier de l'insécurité accidentelle. Deuxièmement, il existe déjà des bibliothèques pour ce faire, vous n'avez donc pas besoin de coder beaucoup et pouvez être immunisé contre les attaques par canal latéral. Enfin, vous pouvez permettre à quiconque d'aller inverser F(x) sans que personne ne puisse réellement calculer F (x).
Un détail - vous ne devriez certainement pas utiliser le type int standard ici. Même avec des entiers 64 bits, il y a si peu de combinaisons possibles qu'un attaquant pourrait simplement essayer de tout inverser jusqu'à ce qu'il trouve le cryptage F(y) pour certains y même s'il ne le fait pas '' Je ne suggère pas d'utiliser quelque chose comme une valeur de 512 bits, car même une attaque de science-fiction ne serait pas en mesure de forcer cela.
J'espère que cela t'aides!
Générez une clé symétrique privée à utiliser dans votre application et cryptez votre entier avec. Cela satisfera les trois exigences, y compris la plus difficile # 3: il faudrait deviner votre clé afin de briser votre schéma.