Hashing di lunghezza arbitrario


16

Considerate si dispone di una funzione di hash H che prende stringhe di lunghezza 2n e restituisce stringhe di lunghezza n ed ha la proprietà bello che è resistente collisione , vale a dire che è difficile trovare due stringhe differenti s≠s′ con lo stesso hash H(s)=H(s′) .

Ora vorresti costruire una nuova funzione hash H′ che prende stringhe di lunghezza arbitraria e le mappa su stringhe di lunghezza n , pur essendo resistente alle collisioni.

Fortunatamente per te, già nel 1979 è stato pubblicato un metodo ora noto come costruzione Merkle – Damgård che ottiene esattamente questo.

Il compito di questa sfida sarà quello di implementare questo algoritmo, quindi daremo prima un'occhiata a una descrizione formale della costruzione Merkle-Damgård, prima di passare attraverso un esempio passo-passo che dovrebbe mostrare che l'approccio è più semplice di potrebbe apparire all'inizio.

Dato un numero intero n>0 , una funzione hash H come descritto sopra e una stringa di input s di lunghezza arbitraria, la nuova funzione hash H′ esegue le seguenti operazioni:

  • Impostare l=|s|, la lunghezza di s , e dividere s in blocchi di lunghezza n , riempiendo l'ultimo blocco con zeri finali, se necessario. Questo produce m=⌈ln⌉molti pezzi che sono etichettatic1,c2,…,cm.
  • Aggiungere un pezzo principale ed una posteriore c0 e cm+1 , dove c0 è una stringa costituita n zeri e cm+1 è n in binario, riempito con porta zeri alla lunghezza n .
  • Ora applica ripetutamente H al blocco corrente ci aggiunto al risultato precedente ri−1 : ri=H(ri−1ci) , dove r0=c0 . (Questo passaggio potrebbe essere più chiaro dopo aver visto l'esempio di seguito.)
  • L'output di H′ è il risultato finale rm+1 .

L'obiettivo

Scrivere un programma o funzione che prende in ingresso un intero positivo n , una funzione hash H come scatola nera e una stringa non vuota s e restituisce lo stesso risultato H′ sugli stessi ingressi.

Questo è , quindi vince la risposta più breve in ogni lingua.

Esempio

Diciamo n=5 , quindi la nostra data funzione di hash H prende stringhe di lunghezza 10 e restituisce stringhe di lunghezza 5.

  • Dato un input di s="Programming Puzzles" , otteniamo i seguenti blocchi: s1="Progr" , s2="ammin" , s3="g Puz" es4="zles0" . Nota ches4 doveva essere imbottito alla lunghezza 5 con uno zero finale.
  • c0="00000" is just a string of five zeros and c5="00101" is five in binary (101), padded with two leading zeros.
  • Ora i blocchi sono combinati con H :
    r0=c0="00000"
    r1=H(r0c1)=H("00000Progr")
    r2=H(r1c2)=H(H("00000Progr")"ammin") H ( H ( " 00000Progr " ) " ammin " ) " g Puz ") rr3=H(r2c3)=H(H(H("00000Progr")"ammin")"g Puz")
    r4=H(r3c4)=H(H(H(H("00000Progr")"ammin")"g Puz")"zles0")
    r5=H(r4c5)=H(H(H(H(H("00000Progr")"ammin")"g Puz")"zles0")"00101")
  • r5 is our output.

Let's have a look how this output would look depending on some choices1 for H:

  • H("0123456789")="13579", i.e. H just returns every second character, we get:
    r1=H("00000Progr")="00Por"
    r2=H("00Porammin")="0oamn"
    r3=H("0oamng Puz")="omgPz"
    r4=H("omgPzzles0")="mPze0"
    r5=H("mPze000101")="Pe011"
    So "Pe011" needs to be the output if such a H is given as black box function.
  • If H simply returns the first 5 chars of its input, the output of H′ is "00000". Similarly if H returns the last 5 chars, the output is "00101".
  • If H multiplies the character codes of its input and returns the first five digits of this number, e.g. H("PPCG123456")="56613", then H′("Programming Puzzles")="91579".

1 For simplicity, those H are actually not collision resistant, though this does not matter for testing your submission.



I must say it's fun that the example given has the last 'full' hash be of "OMG Puzzles!" effectively omgPzzles0. Well chosen example input!
— LambdaBeta

Can we assume some flexibility on the input format for H (e.g. it takes two strings of length n, or a longer string of which it only considers the first 2n characters)?
— Delfad0r

Are space characters, e.g., between "g P" valid output?
— guest271314

@guest271314 If the space is part of the resulting hash, it needs to be outputted. If the hash is actually "gP", you may not output a space inbetween.
— Laikoni

Risposte:


7

Haskell, 91 90 86 bytes

n!h|let a='0'<$[1..n];c?""=c;c?z=h(c++take n(z++a))?drop n z=h.(++mapM(:"1")a!!n).(a?)

Try it online!

Explanation

a='0'<$[1..n]

Just assigns the string "00...0" ('0' n times) to a


c?""=c
c?z=h(c++take n(z++a))?drop n z

The function ? implements the recursive application of h: c is the hash we have obtained so far (length n), z is the rest of the string. If z is empty then we simply return c, otherwise we take the first n characters of z (possibly padding with zeros from a), prepend c and apply h. This gives the new hash, and then we call ? recursively on this hash and the remaining characters of z.


n!h=h.(++mapM(:"1")a!!n).(a?)

The function ! is the one actually solving the challenge. It takes n, h and s (implicit) as inputs. We compute a?s, and all we have to do is append n in binary and apply h once more. mapM(:"1")a!!n returns the binary representation of n.


1
let in a guard is shorter than using where: Try it online!
— Laikoni

2
It looks like mapM(\_->"01")a can be mapM(:"1")a.
— xnor

7

R, 159 154 bytes

function(n,H,s,`?`=paste0,`*`=strrep,`/`=Reduce,`+`=nchar,S=0*n?s?0*-(+s%%-n)?"?"/n%/%2^(n:1-1)%%2)(function(x,y)H(x?y))/substring(S,s<-seq(,+S,n),s--n-1)

Try it online!

Yuck! Answering challenges in R is never pretty, but this is horrible. This is an instructive answer on how not to write "normal" R code...

Thanks to nwellnhof for fixing a bug, at a cost of 0 bytes!

Thanks to J.Doe for swapping the operator aliasing to change the precedence, good for -4 bytes.

The explanation below is for the previous version of the code, but the principles remain the same.

function(n,H,s,               # harmless-looking function arguments with horrible default arguments 
                              # to prevent the use of {} and save two bytes
                              # then come the default arguments,
                              # replacing operators as aliases for commonly used functions:
 `+`=paste0,                  # paste0 with binary +
 `*`=strrep,                  # strrep for binary *
 `/`=Reduce,                  # Reduce with binary /
 `?`=nchar,                   # nchar with unary ?
 S=                           # final default argument S, the padded string:
  0*n+                        # rep 0 n times
  s+                          # the original string
  0*-((?s)%%-n)+              # 0 padding as a multiple of n
  "+"/n%/%2^(n:1-1)%%2)       # n as an n-bit number
                              # finally, the function body:
 (function(x,y)H(x+y)) /      # Reduce/Fold (/) by H operating on x + y
  substring(S,seq(1,?S,n),seq(n,?S,n))  # operating on the n-length substrings of S

I think 0*(n-(?s)%%n) doesn't work if n divides s evenly. But 0*-((?s)%%-n) should work.
— nwellnhof

@nwellnhof ah, of course, thank you, fixed.
— Giuseppe

Minor changes, 155 bytes
— J.Doe

1
@J.Doe nice! I saved another byte since seq has 1 as its from argument by default.
— Giuseppe

3

C (gcc), 251 bytes

#define P sprintf(R,
b(_){_=_>1?10*b(_/2)+_%2:_;}f(H,n,x)void(*H)(char*);char*x;{char R[2*n+1],c[n+1],*X=x;P"%0*d",n,0);while(strlen(x)>n){strncpy(c,x,n);x+=n;strcat(R,c);H(R);}P"%s%s%0*d",R,x,n-strlen(x),0);H(R);P"%s%0*d",R,n,b(n));H(R);strcpy(X,R);}

Try it online!

Not as clean as the bash solution, and highly improvable.

The function is f taking H as a function that replaces its string input with that string's hash, n as in the description, and x the input string and output buffer.

Description:

#define P sprintf(R,     // Replace P with sprintf(R, leading to unbalanced parenthesis
                         // This is replaced and expanded for the rest of the description
b(_){                    // Define b(x). It will return the integer binary expansion of _
                         // e.g. 5 -> 101 (still as integer)
  _=_>1?                 // If _ is greater than 1
    10*b(_/2)+_%2        // return 10*binary expansion of _/2 + last binary digit
    :_;}                 // otherwise just _
f(H,n,x)                 // Define f(H,n,x)
  void(*H)(char*);       // H is a function taking a string
  char*x; {              // x is a string
  char R[2*n+1],c[n+1],  // Declare R as a 2n-length string and c as a n-length string
  *X=x;                  // save x so we can overwrite it later
  sprintf(R,"%0*d",n,0); // print 'n' 0's into R
  while(strlen(x)>n){    // while x has at least n characters
    strncpy(c,x,n);x+=n; // 'move' the first n characters of x into c
    strcat(R,c);         // concatenate c and R
    H(R);}               // Hash R
  sprintf(R,"%s%s%0*d"   // set R to two strings concatenated followed by some zeroes
    R,x,                 // the two strings being R and (what's left of) x
    n-strlen(x),0);      // and n-len(x) zeroes
  H(R);                  // Hash R
  sprintf(R,"%s%*d",R,n, // append to R the decimal number, 0 padded to width n
    b(n));               // The binary expansion of n as a decimal number
  H(R);strcpy(X,R);}     // Hash R and copy it into where x used to be


I think: 227 bytes (going off of ceilingcat's comment)
— Zacharý

3

Ruby, 78 bytes

->n,s,g{(([?0*n]*2*s).chop.scan(/.{#{n}}/)+["%0#{n}b"%n]).reduce{|s,x|g[s+x]}}

Try it online!

How it works:

([?0*n]*2*s).chop    # Padding: add leading and trailing 
                     # zeros, then remove the last one
.scan(/.{#{n}}/)     # Split the string into chunks
                     # of length n
+["%0#{n}b"%n]       # Add the trailing block
.reduce{|s,x|g[s+x]} # Apply the hashing function
                     # repeatedly


2

Bash , 127-ε byte

Z=`printf %0*d $1` R=$Z
while IFS= read -rn$1 c;do R=$R$c$Z;R=`H<<<${R::2*$1}`;done
H< <(printf $R%0*d $1 `bc <<<"obase=2;$1"`)

Provalo online!

Funziona come un programma / funzione / script / frammento. H deve essere risolvibile in un programma o una funzione che eseguirà l'hash. N è l'argomento. Esempio di chiamata:

$ H() {
>   sed 's/.\(.\)/\1/g'
> }
$ ./wherever_you_put_the_script.sh 5 <<< "Programming Puzzles"  # if you add a shebang
Pe011

Descrizione:

Z=`printf %0*d $1`

Questo crea una stringa di $1zero. Funziona chiamando printf e dicendogli di stampare un numero intero riempito con una larghezza dell'argomento aggiuntiva . L'argomento in più che passiamo è $1l'argomento al programma / funzione / script che memorizza n.

R=$Z

Questo semplicemente copia Z, la nostra stringa zero, in R, la nostra stringa di risultato, in preparazione del ciclo di hashing.

while IFS= read -rn$1 c; do

Questo passa sopra l'input ogni $1(n) caratteri caricando i caratteri letti in c. Se l'ingresso termina, allora c finisce semplicemente troppo corto. L' ropzione garantisce che eventuali caratteri speciali nell'input non vengano interpretati in modo bash. Questo è il -εtitolo - che rnon è strettamente necessario, ma rende la funzione più precisa per l'input.

R=$R$c$Z

Questo concatena gli n caratteri letti dall'input a R insieme agli zero per il riempimento (troppi zero per ora).

R=`H<<<${R::2*$1}`;done

Questo utilizza una stringa qui come input per la funzione hash. Il contenuto ${R::2*$1}è una sostituzione di parametro bash in qualche modo esoterica che recita: R, a partire da 0, solo 2n caratteri.

Qui il ciclo termina e finiamo con:

H< <(printf $R%0*d $1 `bc <<<"obase=2;$1"`)

Here the same format string trick is used to 0 pad the number. bc is used to convert it to binary by setting the output base (obase) to 2. The result is passed to the hash function/program whose output is not captured and thus is shown to the user.


Why "127-ε"? Why not just "127"?
— Solomon Ucko

Non lo so. Ero sul recinto per la necessità della rbandiera. Immaginavo che 1 byte non avesse importanza, ma se premuto potevo raderlo.
— LambdaBeta,

Per il readcomando?
— Solomon Ucko,

Because without it a `` in the input will be interpreted instead of ignored, so they'd have to be escaped.
— LambdaBeta

Maybe add a note about that?
— Solomon Ucko

2

Pyth, 24 bytes

Dal momento che Pyth non consente l'utilizzo di H per un nome di funzione, lo uso yinvece.

uy+GH+c.[E=`ZQQ.[ZQ.BQ*Z

Provalo online! L'esempio è con la versione "ogni secondo carattere" di H.


2

Perl 6 , 79 68 byte

{reduce &^h o&[~],comb 0 x$^n~$^s~$n.fmt("%.{$n-$s.comb%-$n}b"): $n}

Provalo online!

Spiegazione

{
  reduce         # Reduce with
    &^h o&[~],   # composition of string concat and hash function
    comb         # Split string
      0 x$^n     # Zero repeated n times
      ~$^s       # Append input string s
      ~$n.fmt("  # Append n formatted
        %.       # with leading zeroes,
        {$n             # field width n for final chunk
         -$s.comb%-$n}  # -(len(s)%-n) for padding,
        b")      # as binary number
      :          # Method call with colon syntax
      $n         # Split into substrings of length n
}

1

Pulito , 143 byte

import StdEnv
r=['0':r]
$n h s=foldl(\a b=h(a++b))(r%(1,n))([(s++r)%(i,i+n-1)\\i<-[0,n..length s]]++[['0'+toChar((n>>(n-p))rem 2)\\p<-[1..n]]])

Provalo online!


1

Python 2 , 126 113 byte

lambda n,H,s:reduce(lambda x,y:H(x+y),re.findall('.'*n,'0'*n+s+'0'*(n-len(s)%n))+[bin(n)[2:].zfill(n)])
import re

Provalo online!

-13 grazie a Triggernometry .

Sì, questo è un abominio, perché non posso semplicemente usare un built-in per dividere una stringa in blocchi ...? :-(


codegolf.stackexchange.com/a/173952/55696 Un whileloop è il miglior componente integrato che potessi sperare. 104 byte
— Steven H.

@StevenH. Sì, soprattutto se in realtà ti stai concentrando sul golf stesso. > _>
— Erik the Outgolfer,

'0'*~-ninvece di '0'*(len(s)%n)è più breve (e in realtà corretto per input più brevi).
— nwellnhof,

@nwellnhof Sì, ma sicuramente non è la stessa cosa.
— Erik the Outgolfer,

Forse non ero abbastanza chiaro. La tua soluzione fornisce la risposta sbagliata per stringhe come Programming Puzz(16 caratteri). Sostituendo '0'*(len(s)%n)con '0'*~-ncorrezioni che e salva 7 byte.
— nwellnhof,

1

Python 2 , 106 102 byte

Per una volta, la funzione supera il lambda. -4 byte per una semplice manipolazione della sintassi, grazie a Jo King.

def f(n,H,s):
 x='0'*n;s+='0'*(n-len(s)%n)+bin(n)[2:].zfill(n)
 while s:x=H(x+s[:n]);s=s[n:]
 return x

Provalo online!


Il risultato non dovrebbe essere "Pe011", non "e011"?
— Triggernometria

Che dovrebbe. Fisso!
— Steven H.

Usa i punti e virgola anziché le nuove righe. -4 byte
— Jo King

Non avevo realizzato che funzionasse anche per i loop mentre, grazie!
— Steven H.

1

Japt , 27 byte

òV ú'0 pV¤ùTV)rÈ+Y gOvW}VçT

Provalo!

Non ho trovato alcuna capacità per Japt di assumere le funzioni direttamente come input, quindi questo richiede una stringa che viene interpretata come codice Japt e si aspetta che definisca una funzione. In particolare, OvWaccetta il terzo input e lo interpreta come Japt, quindi lo gchiama. Sostituendo quello con OxWpermette invece l'input come funzione Javascript, o se la funzione fosse (in qualche modo) già memorizzata in W, potrebbe semplicemente essere We salvare 2 byte. Il link sopra mostra l'esempio funzionante diHche prende caratteri in indici dispari, mentre questo è l'esempio "moltiplica i caratteri e prendi le 5 cifre più alte".

A causa del modo in cui Japt accetta input, Ssarà U,nsarà V, eH sarà W

Spiegazione:

òV                             Split U into segments of length V
   ú'0                         Right-pad the short segment with "0" to the same length as the others
       p     )                 Add an extra element:
        V¤                       V as a base-2 string
          ùTV                    Left-pad with "0" until it is V digits long
              r                Reduce...
                        VçT          ...Starting with "0" repeated V times...
               È       }                                                  ...By applying:
                +Y               Combine with the previous result
                   gOvW          And run W as Japt code



0

ok , 41 byte

{(x#48)(y@,)/(0N,x)#z,,/$((x+x!-#z)#2)\x}

Provalo online!

{                                       } /x is n, y is H, z is s.
                          (x+x!-#z)       /number of padding 0's needed + x
                         (         #2)\x  /binary(x) with this length
                      ,/$                 /to string
                    z,                    /append to z
             (0N,x)#                      /split into groups of length x
       (y@,)/                             /foldl of y(concat(left, right))...
 (x#48)                                   /...with "0"*x as the first left string
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.