任意长度散列


16

考虑您有一个哈希函数 H,该哈希函数接收长度为2n字符串并返回长度为n字符串,并且具有耐碰撞的好特性,即很难找到具有相同哈希H的两个不同的字符串sss = Hs 'H(s)=H(s)

现在,您想构建一个新的哈希函数H,该函数采用任意长度的字符串并将它们映射到长度为n字符串,同时仍具有抗碰撞性。

幸运的是,在1979年已经发布了一种现在称为Merkle–Damgård构造的方法,可以实现此目的。

这项挑战的任务是实现该算法,因此在逐步执行示例之前,我们将首先对Merkle–Damgård构造进行正式描述,该示例应表明该方法比以下方法更简单。它可能会首先出现。

给定一些n>0整数,如上所述的哈希函数H和任意长度的输入字符串s,新的哈希函数H执行以下操作:

  • 设置l=|s|,的长度s和将s拆分为长度为n的块,必要时用尾随零填充最后一个块。这产生m=ln许多块被标记c1,c2,,cm
  • 添加一个前导和尾部块c0cm+1,其中c0是由以下组成的串n零和cm+1n二进制,填充与领先零长度n
  • 现在,将H迭代地应用于附加到先前结果r i - 1的当前块ci上:r i = Hr i - 1 c i,其中r 0 = c 0(查看下面的示例后,此步骤可能会更加清楚。)ri1ri=H(ri1ci)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)=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是我们的输出。

让我们来看看这个输出是什么样子取决于一些选择1H

  • 如果H("0123456789")="13579",即H仅返回第二个字符,则得到:
    r1=H("00000Progr")="00Por"
    r2=H("00Porammin")="0oamn"
    r3=H("0oamng Puz")="omgPz"
    r4=H("omgPzzles0")="mPze0"
    r5=H("mPze000101")="Pe011"
    因此,如果给定这样的 H作为黑盒功能,则必须输出"Pe011"H
  • 如果H简单地返回第5个字符的输入的,输出H"00000"。同样,如果H返回最后5个字符,则输出为"00101"
  • 如果H乘以其输入的字符代码并返回该数字的前五位数字,例如H("PPCG123456")="56613",则H("Programming Puzzles")="91579"

1为简单起见,这些H实际上不是抗碰撞的,尽管这对于测试提交并不重要。



我必须说,给出的示例的最后一个“完整”哈希值是“ OMG Puzzles!”很有趣。有效地omgPzzles0。精心选择的示例输入!
LambdaBeta

我们是否可以假设H的输入格式有一些灵活性(例如,它需要两个长度为n的字符串,或者更长的字符串仅考虑前2n个字符)?
Delfad0r

例如“ g P”之间的空格字符是否有效?
guest271314

@ guest271314如果空格是生成的哈希的一部分,则需要将其输出。如果散列实际上是“ gP”,则您之间可能不会输出空格。
Laikoni '18

Answers:


7

Haskell91 90 86字节

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

在线尝试!

说明

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

只需将字符串"00...0"'0' n次)分配给a


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

该函数?实现了h:的递归应用:c是到目前为止我们获得的哈希(长度n),z是字符串的其余部分。如果z为空,则简单地返回c,否则我们采用的前n字符z(可能是从中填充零a),前置c并应用h。这给出了新的哈希,然后我们?递归调用此哈希和的其余字符z


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

功能!是真正解决挑战的功能。这需要nhs(隐含的)作为输入。我们计算a?s,而我们要做的就是追加n到二进制文件中并h再次应用。mapM(:"1")a!!n返回ñ的二进制表示形式。


1
let警卫比使用where:短:在线尝试!
Laikoni '18

2
看起来mapM(\_->"01")a可以mapM(:"1")a
xnor18年

7

R159154字节

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个字节!

感谢J.Doe交换运算符别名以更改优先级,适用于-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

我认为0*(n-(?s)%%n)如果n均分s是行不通的。但是0*-((?s)%%-n)应该可以。
nwellnhof

@nwellnhof啊,当然,谢谢,固定。
朱塞佩

微小变化,155字节
J.Doe,

1
@ J.Doe太好了!我救另一个字节,因为seq1它的from默认参数。
朱塞佩

3

C(gcc),251个字节

#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);}

在线尝试!

不像bash解决方案那么干净,并且非常可改进。

该函数fH作为一个函数,它的替换字符串输入以该字符串的哈希值,n如在说明书中,并x输入字符串和输出缓冲器。

描述:

#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


我认为:227个字节(ceilingcat的评论要去关)
扎卡里

3

红宝石,78个字节

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

在线尝试!

怎么运行的:

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

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

在线尝试!

这可以用作程序/功能/脚本/代码段。H必须对将执行哈希的程序或函数可解析。N是参数。示例调用:

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

描述:

Z=`printf %0*d $1`

这将创建一个$1零字符串。这可以通过调用printf并告诉它打印填充为额外参数宽度的整数来实现。我们传递的额外参数是$1,用于存储n的程序/函数/脚本的参数。

R=$Z

这只是将我们的零字符串Z复制到我们的结果字符串R中,以准备哈希循环。

while IFS= read -rn$1 c; do

这会每隔$1(n)个字符循环输入一次,将读取的字符加载到c中。如果输入结束,则c仅以太短而结束。该r选项可确保输入中的任何特殊字符都不会被bash解释。这是标题中的- r并非绝对必要,但可使函数更准确地匹配输入。

R=$R$c$Z

这会将从输入读取到R的n个字符与零填充在一起(现在填充零)。

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

这使用here字符串作为哈希函数的输入。内容${R::2*$1}是一个有点深奥的bash参数替换,内容为:R,从0开始,只有2n个字符。

循环到此结束,然后我们完成:

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

此处,使用相同格式的字符串技巧将数字0填充。bc通过将输出基数(obase)设置为2来将其转换为二进制。结果被传递到哈希函数/程序,其输出未捕获,因此显示给用户。


为什么是“127-ε”?为什么不只是“ 127”?
所罗门·乌科

我不知道。我对那面r旗帜的必要性表示怀疑。我认为1字节并不重要,但是如果按一下,我就可以剃掉它。
LambdaBeta

对于read命令?
所罗门·乌科

因为没有它,输入中的``将被解释而不是被忽略,因此它们必须被转义。
LambdaBeta

也许对此添加注释?
所罗门·乌科

2

Pyth,24个字节

由于Pyth不允许将H用作函数名称,因此我y改用了。

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

在线尝试!例子是H的“第二个字符”版本。


2

Perl 6的79 68个字节

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

在线尝试!

说明

{
  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

干净的 143字节

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

在线尝试!


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归功于Triggernometry

是的,这是一个可憎的事情,为什么我不能只使用内置函数将字符串拆分成块...?:-(


codegolf.stackexchange.com/a/173952/55696while环是最好的内建我希望。104字节
Steven H.

@StevenH。是的,尤其是如果您实际上专注于高尔夫本身。> _>
暴民埃里克(Erik the Outgolfer)'18年

'0'*~-n而不是'0'*(len(s)%n)更短(实际上对于更短的输入是正确的)。
nwellnhof

@nwellnhof是的,但是绝对不是同一回事。
暴民埃里克(Erik the Outgolfer)'18年

也许我还不够清楚。您的解决方案为字符串Programming Puzz(16个字符)给出了错误的答案。替换'0'*(len(s)%n)'0'*~-n可节省7个字节的修复程序。
nwellnhof

1

Python 2中106个 102字节

函数一次超越了lambda。-4个字节,用于简单的语法操作,这要归功于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

在线尝试!


结果不应该是“ Pe011”,而不是“ e011”吗?
Triggernometry '18

应该的。固定!
史蒂文H.

使用分号代替换行符。-4字节
乔金

我也没意识到在while循环中也有用,谢谢!
史蒂文H.

1

Japt,27个字节

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

尝试一下!

我尚未发现Japt可以直接将函数用作输入的任何功能,因此这需要一个字符串,该字符串被解释为Japt代码并希望它定义一个函数。具体来说,OvW获取第三个输入并将其解释为Japt,然后g调用它。替换为则OxW允许输入作为Javascript函数,或者如果该函数已经(某种程度上)已经存储在W中,则可能只是W保存2个字节。上面的链接具有以下工作示例H它以奇数索引处的字符为例,而是“乘以字符代码并采用5个最高位”的示例。

由于Japt接受输入的方式, sUñ将是V,并且H 将会 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

OK,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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.