Beliebiges Längen-Hashing


16

Betrachten Sie eine haben Hash - Funktion H die Strings der Länge nimmt 2n und kehrt Strings der Länge n und hat die schöne Eigenschaft , dass es resistent gegen Kollisionen , dh es ist schwierig , zwei verschiedene Zeichenketten zu finden ss mit dem gleichen Hash - H(s)=H(s) .

Sie möchten jetzt eine neue Hash-Funktion H erstellen , die Zeichenfolgen beliebiger Länge auf Zeichenfolgen der Länge n abbildet und dabei trotzdem kollisionssicher ist.

Zum Glück schon 1979 eine Methode, die heute als Merkle-Damgård-Konstruktion bekannt ist genau dies erreicht.

Die Aufgabe dieser Herausforderung wird es sein, diesen Algorithmus zu implementieren. Wir werden uns daher zunächst die formale Beschreibung der Merkle-Damgård-Konstruktion ansehen, bevor wir uns einem schrittweisen Beispiel zuwenden, das zeigen soll, dass der Ansatz einfacher ist als es könnte zuerst erscheinen.

Wenn eine ganze Zahl n>0 , eine Hash-Funktion H wie oben beschrieben und eine Eingabezeichenfolge s beliebiger Länge gegeben sind, führt die neue Hash-Funktion H Folgendes aus:

  • Setze l=|s|, die Länge von s und die Aufteilung von s in Abschnitte der Länge n , wobei der letzte Abschnitt bei Bedarf mit nachgestellten Nullen aufgefüllt wird. Dies ergibt m=lnviele Stücke, die mitc1,c2,,cm.
  • Hinzufügen eines vorderen und einen hinteren chunk c0 und cm+1 , wobei c0 eine Zeichenfolge ist , die aus n Nullen und cm+1 ist , n im Binär-, aufgefüllt mit führenden Nullen auf Länge n .
  • Wenden Sie nun iterativ H auf den aktuellen Block ci an, der an das vorherige Ergebnis ri1 angehängt ist : ri=H(ri1ci) , wobei r0=c0 . (Dieser Schritt ist möglicherweise klarer, nachdem Sie sich das folgende Beispiel angesehen haben.)
  • Die Ausgabe von H ist das Endergebnis rm+1 .

Die Aufgabe

Schreiben Sie ein Programm oder eine Funktion, die eine positive ganze Zahl n , eine Hash-Funktion H als Blackbox und eine nicht leere Zeichenfolge s als Eingabe verwendet und an denselben Eingängen dasselbe Ergebnis wie H zurückgibt .

Das ist , also gewinnt die kürzeste Antwort in jeder Sprache.

Beispiel

Nehmen wir an, n=5 , also nimmt unsere gegebene Hash-Funktion H Zeichenfolgen der Länge 10 und gibt Zeichenfolgen der Länge 5 zurück.

  • Bei einer Eingabe von s="Programming Puzzles" wir die folgenden Blöcke: s1="Progr" , s2="ammin" , s3="g Puz" und s4="zles0" . Beachten Sie, dass s4 mit einer nachgestellten Null auf Länge 5 aufgefüllt werden musste.
  • c0="00000" ist nur eine Folge von fünf Nullen undc5="00101" ist fünf in binär (101 ), aufgefüllt mit zwei führenden Nullen.
  • Nun werden die Brocken mit kombiniertem H :
    r0=c0="00000"
    r1=H(r0c1)=H("00000Progr")
    r2=H(r1c2)=H(H("00000Progr")"ammin") r3=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 ist unser Output.

Lassen Sie uns einen Blick darauf werfen, wie diese Ausgabe aussehen würde, abhängig von einigen Optionen 1 für H :

  • Wenn H("0123456789")="13579" , dh H nur jedes zweite Zeichen zurück, erhalten wir:
    r1=H("00000Progr")="00Por"
    r2=H("00Porammin")="0oamn"
    r3=H("0oamng Puz")="omgPz"
    r4=H("omgPzzles0")="mPze0"
    r5=H("mPze000101")="Pe011"
    So"Pe011" Bedürfnisse der Ausgang auf, wennsolchesH als BlackBoxFunktion gegeben ist.
  • Wenn H einfach die ersten 5 Zeichen seines Eingangszurückkehrt, die Ausgabe des H ist "00000" . In ähnlicher Weise ist die Ausgabe "00101", wenn H die letzten 5 Zeichen "00101" .
  • Wenn H die Zeichencodes seiner Eingabe multipliziert und die ersten fünf Ziffern dieser Zahl zurückgibt, z. B. H("PPCG123456")="56613" , dann ist H("Programming Puzzles")="91579" .

1 Der Einfachheit halber sind diese H tatsächlich nicht kollisionssicher, obwohl dies für die Prüfung Ihres Beitrags keine Rolle spielt.



Ich muss sagen, es macht Spaß, dass das angegebene Beispiel den letzten "vollen" Hash von "OMG Puzzles!" effektiv omgPzzles0. Gut gewählter Beispieleingang!
LambdaBeta

Können wir eine gewisse Flexibilität beim Eingabeformat für H annehmen (z. B. zwei Zeichenfolgen mit der Länge n oder eine längere Zeichenfolge, bei der nur die ersten 2n Zeichen berücksichtigt werden)?
Delfad0r

Sind Leerzeichen, zB zwischen "g P", gültig?
guest271314

@ guest271314 Wenn das Leerzeichen Teil des resultierenden Hashs ist, muss es ausgegeben werden. Wenn der Hash tatsächlich "gP" ist, können Sie möglicherweise kein Leerzeichen dazwischen ausgeben.
Laikoni

Antworten:


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?)

Probieren Sie es online!

Erläuterung

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

Weist einfach die Zeichenfolge "00...0"( '0' n mal) zua


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

Die Funktion ?implementiert die rekursive Anwendung von h: cist der bisher erhaltene Hash (Länge n ), zist der Rest des Strings. Wenn zleer ist, kehren wir einfach zurück c, ansonsten nehmen wir die ersten n Zeichen von z(möglicherweise mit Nullen aaufgefüllt von ), stellen sie voran cund wenden sie an h. Dies ergibt den neuen Hash, und dann rufen wir ?diesen Hash und die verbleibenden Zeichen von rekursiv auf z.


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

Die Funktion !ist diejenige, die die Herausforderung tatsächlich löst. Es dauert n, hund s(implizit) als Eingänge. Wir berechnen a?s, und alles, was wir tun müssen, ist, nbinär anzuhängen und erneut anzuwenden h. mapM(:"1")a!!ngibt die binäre Darstellung von n .


1
letin einer Wache ist kürzer als mit where: Probieren Sie es online!
Laikoni

2
Es sieht aus wie sein mapM(\_->"01")akann 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)

Probieren Sie es online!

Yuck! Das Beantworten von Herausforderungen in R ist nie schön, aber das ist schrecklich. Dies ist eine lehrreiche Antwort darauf, wie man keinen "normalen" R-Code schreibt ...

Vielen Dank an nwellnhof für die Behebung eines Fehlers, zu einem Preis von 0 Bytes!

Vielen Dank an J.Doe für das Auswechseln des Operator-Aliasing, um die Priorität zu ändern. Gut für -4 Bytes.

Die folgende Erklärung gilt für die vorherige Version des Codes, die Prinzipien bleiben jedoch gleich.

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

Ich denke, 0*(n-(?s)%%n)funktioniert nicht, wenn n s gleichmäßig teilt. Sollte aber 0*-((?s)%%-n)funktionieren.
Nwellnhof

@nwellnhof ah, natürlich danke, behoben.
Giuseppe

Kleinere Änderungen, 155 Bytes
J.Doe

1
@ J.Doe schön! Ich habe ein weiteres Byte gespeichert, da seqes standardmäßig 1als fromArgument hat.
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);}

Probieren Sie es online!

Nicht so sauber wie die Bash-Lösung und hochgradig verbesserungsfähig.

Die Funktion fnimmt Hals eine Funktion, die ihre Zeichenfolgeneingabe durch den Hash dieser Zeichenfolge, nwie in der Beschreibung, und xdie Eingabezeichenfolge und den Ausgabepuffer ersetzt.

Beschreibung:

#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


Ich denke: 227 Bytes (geht von CeilingCats Kommentar ab)
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]}}

Probieren Sie es online!

Wie es funktioniert:

([?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-ε Bytes

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"`)

Probieren Sie es online!

Dies funktioniert als Programm / Funktion / Skript / Snippet. H muss in ein Programm oder eine Funktion auflösbar sein, die das Hashing ausführt. N ist das Argument. Beispielaufruf:

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

Beschreibung:

Z=`printf %0*d $1`

Dies erzeugt eine Folge von $1Nullen. Dies funktioniert, indem printf aufgerufen und angewiesen wird, eine Ganzzahl zu drucken, die mit einer zusätzlichen Argumentbreite aufgefüllt ist . Das zusätzliche Argument, das wir übergeben, ist $1das Argument für das Programm / die Funktion / das Skript, in dem n gespeichert ist.

R=$Z

Dies kopiert lediglich Z, unsere Nullzeichenfolge, nach R, unsere Ergebniszeichenfolge, um die Hashing-Schleife vorzubereiten.

while IFS= read -rn$1 c; do

Dies durchläuft alle $1(n) Zeichen die Eingabe und lädt die gelesenen Zeichen in c. Wenn die Eingabe endet, endet c einfach zu kurz. Die rOption stellt sicher, dass Sonderzeichen in der Eingabe nicht vom Bash interpretiert werden. Dies ist der Titel, der rnicht unbedingt erforderlich ist, aber die Funktion genauer an die Eingabe anpasst.

R=$R$c$Z

Dies verkettet die von der Eingabe in R gelesenen n Zeichen mit Nullen zum Auffüllen (vorerst zu viele Nullen).

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

Dies verwendet einen Here-String als Eingabe für die Hash-Funktion. Der Inhalt ${R::2*$1}ist eine etwas esoterische Bash-Parameter-Ersetzung, die lautet: R, beginnend mit 0, nur 2n Zeichen.

Hier endet die Schleife und wir beenden mit:

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

Hier wird der gleiche Formatstringtick verwendet, um die Zahl mit 0 aufzufüllen. bcwird verwendet, um es in binär umzuwandeln, indem die Ausgabebasis (obase) auf 2 gesetzt wird. Das Ergebnis wird an die Hash-Funktion / das Hash-Programm übergeben, dessen Ausgabe nicht erfasst und somit dem Benutzer angezeigt wird.


Warum "127-ε"? Warum nicht einfach "127"?
Solomon Ucko

Ich weiß es nicht. Ich war auf dem Zaun wegen der Notwendigkeit der rFlagge. Ich dachte, dass 1 Byte keine Rolle spielt, aber wenn es gedrückt wird, kann ich es rasieren.
LambdaBeta

Für den readBefehl?
Solomon Ucko

Denn ohne sie wird ein `` in der Eingabe interpretiert, anstatt ignoriert zu werden, sodass sie maskiert werden müssten.
LambdaBeta

Vielleicht eine Anmerkung dazu hinzufügen?
Solomon Ucko

2

Pyth , 24 Bytes

Da Pyth nicht zulässt, dass H für einen Funktionsnamen verwendet wird, verwende ich ystattdessen.

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

Probieren Sie es online! Beispiel ist mit der "Every Second Character" -Version von H.


2

Perl 6 , 79 68 Bytes

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

Probieren Sie es online!

Erläuterung

{
  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

Python 2 , 126 113 Bytes

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

Probieren Sie es online!

-13 dank Triggernometrie .

Ja, das ist ein Gräuel, warum kann ich nicht einfach eine eingebaute Funktion verwenden, um einen String in Stücke zu teilen ...? :-(


codegolf.stackexchange.com/a/173952/55696 Eine whileSchleife ist die beste, die ich mir erhoffen kann. 104 Bytes
Steven H.

@StevenH. Ja, besonders wenn Sie sich auf das Golfen konzentrieren. > _>
Erik der Outgolfer

'0'*~-nstatt '0'*(len(s)%n)ist kürzer (und eigentlich korrekt für kürzere Eingaben).
Nwellnhof

@nwellnhof Ja, aber es ist definitiv nicht dasselbe.
Erik der Outgolfer

Vielleicht war ich nicht klar genug. Ihre Lösung gibt die falsche Antwort für Zeichenfolgen wie Programming Puzz(16 Zeichen). Ersetzen '0'*(len(s)%n)mit '0'*~-nbehebt das und spart 7 Bytes.
Nwellnhof

1

Python 2 , 106 102 Bytes

Ausnahmsweise übertrifft die Funktion den Lambda. -4 Bytes für einfache Syntaxmanipulation dank 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

Probieren Sie es online!


Sollte das Ergebnis nicht 'Pe011' sein, nicht 'e011'?
Triggernometrie

Das sollte es. Fest!
Steven H.

Verwenden Sie Semikolons anstelle von Zeilenumbrüchen. -4 Bytes
Jo King

Ich wusste nicht, dass das auch für while-Schleifen funktioniert, danke!
Steven H.

1

Japt , 27 Bytes

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

Versuch es!

Ich habe keine Möglichkeit für Japt gefunden, Funktionen direkt als Eingabe zu verwenden. Daher wird eine Zeichenfolge verwendet, die als Japt-Code interpretiert wird und eine Funktion definiert. OvWNimmt speziell die dritte Eingabe, interpretiert sie als Japt und gruft sie dann auf. OxWWenn Sie dies durch ersetzen, wird die Eingabe stattdessen als Javascript-Funktion ermöglicht, oder wenn die Funktion (irgendwie) bereits in W gespeichert wäre, könnten Sie einfach W2 Bytes einsparen. Der obige Link hat das funktionierende Beispiel vonHdas braucht Zeichen bei ungeraden Indizes, während dies das Beispiel "Multipliziere Zeichencodes und nimm die 5 höchsten Ziffern" ist.

Aufgrund der Art und Weise Japt Eingaben nimmt, swird sein U,nwird sein V, undH wird sein W

Erläuterung:

ò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 bytes

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

Probieren Sie es 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.