Произвольная длина хэширования


16

Предположим, у вас есть хеш-функция H которая принимает строки длиной 2n и возвращает строки длины и имеет приятное свойство - она устойчива к столкновениям , то есть трудно найти две разные строки с одинаковым хешем .ns≠s′H(s)=H(s′)

Теперь вы хотели бы создать новую хеш-функцию которая принимает строки произвольной длины и отображает их в строки длины , в то же время сохраняя устойчивость к столкновениям.H′n

К счастью для вас, уже в 1979 году был опубликован метод, теперь известный как конструкция Меркля-Дамгарда, который достигает именно этого.

Задача этой задачи будет заключаться в реализации этого алгоритма, поэтому сначала мы рассмотрим формальное описание конструкции Меркля – Дамгарда, прежде чем перейти к пошаговому примеру, который должен показать, что подход проще, чем это может появиться сначала.

Учитывая некоторое целое число , хеш-функцию как описано выше, и входную строку произвольной длины, новая хеш-функция выполняет следующие действия:n>0HsH′

  • Установите, длина и разбиение на куски длины n , заполняя последний кусок завершающими нулями, если необходимо. Это дает m = ⌈ ll=|s|ssnm=⌈ln⌉много кусков, которые помечены какc1,c2,…,cm.
  • Добавьте начальный и конечный фрагменты c0 и cm+1 , где c0 - строка, состоящая из n нулей иcm+1 - этодвоичное числоn , дополненноеначальныминулями до длиныn .
  • Теперь итеративно применяем H к текущему фрагменту cя добавленному к предыдущему результату ri−1 : ri=H(ri−1ci) , где r0=c0 . (Этот шаг может быть более понятным после просмотра примера ниже.)
  • Выход H′ является конечным результатом rm+1 .

Задание

Напишите программу или функцию, которая принимает в качестве входных данных положительное целое число n , хеш-функцию H качестве черного ящика и непустую строку s и возвращает тот же результат, что и H′ на тех же входах.

Это , поэтому выигрывает самый короткий ответ на каждом языке.

пример

Допустим, n=5 , поэтому наша заданная хеш-функция H принимает строки длиной 10 и возвращает строки длиной 5.

  • Учитывая ввод s="Programming Puzzles" , мы получаем следующие фрагменты: s1="Progr" , s2="ammin" , s3="g Puz" и s4="zles0" . Обратите внимание, что s4 необходимо дополнить до длины 5 с одним завершающим нулем.
  • c0="00000" - это просто строка из пяти нулей, аc5="00101" - это пять в двоичном виде (101 ), дополненная двумя ведущими нулями.
  • Теперь куски объединяются с H :
    r0=c0="00000"
    r1=H(r0c1)=H("00000Progr")
    r2=H(r1c2)=H(H("00000Progr")"ammin") r3=H(r2c3)знак равноЧАС(ЧАС(ЧАС("00000Progr")«Аммин»)"г пуз")
    р4знак равноЧАС(р3с4)знак равноЧАС(ЧАС(ЧАС(ЧАС("00000Progr")«Аммин»)"г пуз")"Zles0")
    р5знак равноЧАС(р4с5)знак равноЧАС(ЧАС(ЧАС(ЧАС(ЧАС("00000Progr")«Аммин»)"г пуз")"Zles0")"00101")
  • р5 is our output.

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

  • If 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

Ответы:


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
Похоже, mapM(\_->"01")aможно mapM(:"1")a.
— xnor

7

R , 159 154 байт

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)

Попробуйте онлайн!

Тьфу! Отвечать на вызовы в R никогда не бывает красиво, но это ужасно. Это поучительный ответ о том, как не писать "нормальный" R-код ...

Спасибо nwellnhof за исправление ошибки стоимостью 0 байт!

Спасибо Дж. Доу за замену псевдонимов операторов на изменение приоритета, хорошо для -4 байта.

Приведенное ниже объяснение относится к предыдущей версии кода, но принципы остаются теми же.

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

Try it online!

This works as a program/function/script/snippet. H must be resolveable to a program or function that will perform the hashing. N is the argument. Example call:

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

Description:

Z=`printf %0*d $1`

This creates a string of $1 zeroes. This works by calling printf and telling it to print an integer padded to extra argument width. That extra argument we pass is $1, the argument to the program/function/script which stores n.

R=$Z

This merely copies Z, our zero string, to R, our result string, in preparation for the hashing loop.

while IFS= read -rn$1 c; do

This loops over the input every $1 (n) characters loading the read characters into c. If the input ends then c merely ends up too short. The r option ensures that any special characters in the input don't get bash-interpreted. This is the -ε in the title - that r isn't strictly necessary, but makes the function more accurately match the input.

R=$R$c$Z

This concatenates the n characters read from input to R along with zeroes for padding (too many zeroes for now).

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

This uses a here string as input to the hash function. The contents ${R::2*$1} are a somewhat esoteric bash parameter substitution which reads: R, starting from 0, only 2n characters.

Here the loop ends and we finish with:

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

I don't know. I was on the fence about the necessity of the r flag. I figured 1 byte doesn't really matter, but if pushed I could shave it.
— LambdaBeta

For the read command?
— 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

Perl 6, 79 68 bytes

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

Try it online!

Explanation

{
  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 байтов

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

Попробуйте онлайн!

-13 благодаря триггернометрии .

Да, это мерзость, почему я не могу просто использовать встроенный для разделения строки на куски ...? :-(


codegolf.stackexchange.com/a/173952/55696while петля является лучшей встроенным я мог надеяться. 104 байта
— Стивен Х.

@StevenH. Yeah, especially if you're actually focusing on the golfing itself. >_>
— Erik the Outgolfer

'0'*~-n instead of '0'*(len(s)%n) is shorter (and actually correct for shorter inputs).
— nwellnhof

@nwellnhof Yeah, but it's definitely not the same thing.
— Erik the Outgolfer

Может быть, я не был достаточно ясен. Ваше решение дает неправильный ответ для строк типа Programming Puzz(16 символов). Замена '0'*(len(s)%n)с '0'*~-nисправлениями , что и экономит 7 байт.
— nwellnhof

1

Python 2 , 106 102 байта

На этот раз функция превосходит лямбду. -4 байта для простого манипулирования синтаксисом, благодаря Джо Кингу.

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

Попробуйте онлайн!


Разве результат не должен быть «Pe011», а не «e011»?
— Триггонометрия

Это должно быть. Исправлена!
— Стивен Х.

Используйте точки с запятой вместо символов новой строки. -4 байта
— Джо Кинг,

Я не осознавал, что это работает и для циклов while, спасибо!
— Стивен Х.

1

Japt , 27 байт

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

Попытайся!

Я не нашел возможности для Japt принимать функции непосредственно в качестве входных данных, поэтому он принимает строку, которая интерпретируется как код Japt, и ожидает, что она определит функцию. В частности, OvWпринимает третий ввод и интерпретирует его как Japt, а затем gвызывает его. Заменив это OxWразрешением на ввод вместо функции Javascript, или, если функция (каким-то образом) уже была сохранена в W, она может просто Wсохранить 2 байта. Ссылка выше имеет рабочий примерЧАСэто принимает символы с нечетными индексами, в то время как этот пример является "умножением кодов символов и возьмите 5 старших цифр" пример.

Благодаря тому, как Japt принимает входные данные, sбудет U,Nбудет VиЧАС будет W

Объяснение:

ò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

ОК , 41 байт

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

Попробуйте онлайн!

{                                       } /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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.