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 ss 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=lnmolti 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 ri1 : ri=H(ri1ci) , 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.