代码高尔夫:异或加密

37

来自: 加密公司。
: x$*sj4(即你)

如果您愿意接受任务,您需要用最少的按键数创建一个程序,该程序:

  • 接受两个文件名参数(可以是命令行或标准输入),第一个文件包含密钥,第二个文件包含一些消息。这两个文件都是纯文本。

  • 使用XOR加密将密钥应用于消息,覆盖原始文件。

示例:

输入文件:

StackOverflow is Cool

密钥:

Code Golf

加密输出文件的十六进制转储:

0000000: 101b 0506 4b08 1909 1425 030b 1200 2e1c  ....K....%......
0000010: 4c25 2c00 080d 0a                        L%,....

为简单起见,假设文件可以适应内存。


此消息将在5... 4... 3... 2... 1...自行加密。

     #####
    #### _\_  ________
    ##=-[.].]| \      \
    #(    _\ |  |------|
     #   __| |  ||||||||
      \  _/  |  ||||||||
   .--'--'-. |  | ____ |
  / __      `|__|[o__o]|
_(____nm_______ /____\____ 
如果密钥的大小大于或等于消息的大小,并且密钥是通过无偏随机过程生成的,则XOR加密是不可能被破解的。参见:一次性密码本。所以这里没有“差劲的加密”问题。

10
以下是雅虎通使用的加密对话算法。 - Brian
2
上述加密输出文件解密后为“StackOverflow is Coolh*”。 - Jeffrey L Whitledge
5
不,实际上在加密的文件末尾有一个\r\n对。所以很可能是原帖作者实现时出现了bug :) - Thomas
3
只要随机密钥大于消息本身,这实际上是一个很棒的算法。 - cobbal
4
...并且你绝不能重复使用密钥,而且在使用后必须销毁密钥,同时你需要能够管理密钥分发。 - dmckee --- ex-moderator kitten
显示剩余10条评论
23个回答

25

忏悔,13 7个字符(无文件支持),14个字符(有文件支持)

忏悔是我自己设计的一种古怪的基于栈的玩具语言,灵感来自于 J、APL、Golfscript 和 Python。下面是一个简短的解决方案。我会解释它,但现在已经很晚了,这让我感到很困扰,所以我将在明天早上解释它,并发布一个 Silverlight 解释器。

↓↷¦*⊕;€

解释:

↓     Copies the message string back onto the stack
↷    Puts the extra message string to the bottom of stack
¦     Find length of message string
*     Multiply key array by last number - repeats key for at least as long as message
⊕;    Apply XOR between each element corresponding of message array and repeated 
      key array, pushing XOR encoded message to stack
€     Print encoded message string/(char array) as string.

使用方法:

Repent "↓↷¦*⊕;€" "Code Golf" "StackOverflow is Cool" > output.txt

输出(大多数字符不会显示):

Ascii: K    % .L%, 
Hex:   10 1B 05 06 4B 08 19 09 14 25 03 0B 12 00 2E 1C 4C 25 2C 00 08 0D 0A
使用文件的话,可以这样做:
↓↶▲⇄▲↓3↔⇄¦*⊕;▼

语言参考(未完成)

解释器(未完成)


1
我不是那个给你点踩的人,但可能是因为你的解决方案没有进行文件 IO。 - bcat
哦,好的。那我需要添加支持 :( - Callum Rogers
13
我认为你可以用“香蕉”代替“¦⇄¦⇄”,但是也没人能够理解 :D - Dr. belisarius
1
@Yi Jiang 哇!你会说悔改语! :D - Dr. belisarius
1
当然,一个玩具语言可以轻松解决任何比一般语言更短的问题:只需不断地专门化该语言,直到它成为最短的! - Eamon Nerbonne
显示剩余3条评论

22

Perl,40个字符

它有点脆弱。

print$/=!1,($_=<>)^substr<>x 1E4,0,y///c

Perl有一个内置的字符串异或运算符。要解决这个问题,难点在于使两个字符串具有相同的长度。

$/=!1

将"记录分隔符"设置为未定义值,并且不会导致任何内容被打印。使用此设置,文件的readline操作符将读取整个文件。

$_=<>

将包含消息的第一个文件全部加载到变量$_中。

substr <> x 1E4, 0, y///c

从第二个文件(key)中创建另一个字符串,并将其重复添加到自身10,000次。希望(1)这个非常长的字符串比消息字符串更长,且(2)它不会太长以至于导致程序耗尽内存(这就是这个解决方案脆弱之处)。y///c是一种计算 $_ 中字符数的操作,它比使用 length 语句短一个字符。这将使密钥字符串缩短到与消息字符串相同的大小。


相当惊人。你能为我们这些不懂Perl的人提供解释吗? - driis
4
该程序不符合规格,它不会覆盖输入的消息。 - rubber boots

12

C# 190个字符

using System.IO;class a{static void Main(string[] b){var c=File.ReadAllBytes(b[0]);var d=File.ReadAllBytes(b[1]);for(int e=0;e<c.Length;e++) c[e]^=d[e%d.Length];File.WriteAllBytes(b[0],c);}}

1
很棒,至少与Python代码相当。对于.Net冗长的语言来说令人印象深刻。 :) - Russell

8

Python,162个字符

m,r,o=map,raw_input,open
a,b=r(),r()
t,k=m(lambda x:list(o(x).read()[:-1]),[a,b])
o(a,'w').write(''.join(m(chr,m(lambda c:ord(c[0])^ord(c[1]),zip(t,len(t)*k)))))

Python 3,共143个字符

i,o=input,open
a,b=i(),i()
t,k=map(lambda x:list(o(x,'rb').read()[:-1]),[a,b])
o(a,'wb').write(bytes(map(lambda c:c[0]^c[1],zip(t,len(t)*k))))

太棒了。我无法将我的降到290以下 :( - hb2pencil
1
列表推导式比 map/lambda 更短。 - kennytm

8
高尔夫脚本,28个字符
n.+/~:k;.,.)k.,@\/)*<{\(@^}%

使用方法:将消息文件传递给脚本的标准输入,后跟一个换行符,然后是密钥文件:

$ (cat message-file ; echo ; cat key-file) | ruby golfscript.rb poorencrypt.gs

$ (echo StackOverflow is Cool;echo;echo Code Golf) | \
          ruby golfscript.rb poorencrypt.gs > encoded-file
$ (cat encoded-file;echo;echo Code Golf) | ruby golfscript.rb poorencrypt.gs
StackOverflow is Cool
这段代码是使用golfscript.rb对文本进行加密和解密的示例。它将"StackOverflow is Cool"加密并存储在encoded-file中,然后再解密并输出到控制台上。

1
使用Golfscript可以进行文件IO。请参见http://golf.shinh.org/reveal.rb?Remenber+Previous+Input/gnibbler_1269243311&gs - John La Rooy

7

Java, 319 313 310个字符


  • 更新1:char [] c = r(a [0]); char [] k = r(a [1]);替换为char [] c = r(a [0]),k = r(a [1]);,节省了6个字符。

  • 更新2:for(int i = 0; i < c.length; c [i] ^ = k [i ++%k.length]);替换为int i = 0; for(char p:c)c [i] ^ = k [i ++%k.length];,节省了3个字符。


import java.io.*;class X{public static void main(String[]a)throws Exception{char[]c=r(a[0]),k=r(a[1]);int i=0;for(char p:c)c[i]^=k[i++%k.length];Writer w=new FileWriter(a[0]);w.write(c);w.close();}static char[]r(String a)throws Exception{return new BufferedReader(new FileReader(a)).readLine().toCharArray();}}

更易读版本:

import java.io.*;
class X{
 public static void main(String[]a)throws Exception{
  char[]c=r(a[0]),k=r(a[1]);int i=0;for(char p:c)c[i]^=k[i++%k.length];
  Writer w=new FileWriter(a[0]);w.write(c);w.close();
 }
 static char[]r(String a)throws Exception{
  return new BufferedReader(new FileReader(a)).readLine().toCharArray();
 }
}

Java IO非常冗长。将两个文件读取为char[]重构为一个方法可以节省4个字符。是的,关闭(刷新)写入器是绝对必要的。否则文件会被清空。否则它本来有 298 292 289 字符。


3
感谢您在Java代码高尔夫中做出了认真的尝试。现有的代码高尔夫中涉及Java语言的内容实在太少了,希望能有更多相关内容。 - Sean Patrick Floyd
1
我设法节省了3个字符...将for loop更改为int i=0;for(char p:c)c[i]^=k[i++%k.length]; - st0le
我本来要大喊抄袭,但你采取了不同的方法。+1 - TheLQ
我很惊讶你能在500个字符以下完成这样的东西。Java相当冗长。当然要点赞。 - Wayne Werner

6

Python3 - 114个字符

从标准输入中获取参数

a=input().split()
k,t=[open(x,"rb").read()for x in a]
open(a[1],"wb").write(bytes(x^y for x,y in zip(k*len(t),t)))

1
到目前为止,你的解决方案似乎是最短的非神秘解决方案,实际上符合规范(=覆盖输入文件)。 - Heinzi

4

F#, 168 chars

open System.IO
[<EntryPoint>]
let main a=
let k=File.ReadAllBytes a.[1]
let z i v=v^^^k.[i%k.Length]
File.WriteAllBytes(a.[0], Array.mapi z (File.ReadAllBytes a.[0]))
0

注意:大部分是IO操作,关键在于Array.mapi。此外,一些F#专家可能会对这个解决方案进行严厉的批评——我是一名C#程序员,在学习F#时只是出于兴趣。

你可以摆脱入口点/主函数/0,只需将主函数的主体作为“顶级代码”,这是很好的代码高尔夫节省。 - Brian
2
@Brian,我认为(基于2分钟的谷歌搜索),声明主函数是获取命令行参数的最短方式?否则,我必须调用Environment.GetCommandLineArgs,这是整整30个字符。如果有更好的方法,请纠正我 :-) - driis
哦,我错过了那一点 - 见笑了! - Brian
看起来不错,但我认为你颠倒了命令行参数。(密钥文件名应该在消息文件名之前。)此外,在main的函数体中的语句前需要一些缩进(一个空格就足够了)。 - bcat

4

Ruby 72 62 chars

$<.inject{|k,l|l.each_byte{|b|$><<(b^(r=k.slice!0)).chr;k<<r}}

如果我不需要用k=a.chomp;从输入的密钥中去掉\n,我可以节省10个字符。 我还是这样做了。

限制:只处理单行密钥。

工作原理:

$<就像一个包含所有输入文件所有行的数组。

.inject迭代数组,

{|k,l| :在第一次迭代时,参数是密钥行和输入的第一行。

l.each_byte{|b|将输入行的每个字符作为int取出。

$><<表示“打印”

(b^(r.k.slice!0)将'b'与密钥中的第一个字符进行异或运算(然后将其切片并存储在'r'中)

.chr;将整数转换回ASCII码

k<<r将密钥的第一个字符旋转到末尾。

}}该块生成更新后的k,它将用作注入下一次传递的第一个参数;第二个参数将是输入的下一行。


2
所以不要这样做。让换行符成为密钥的一部分,或者使用一个没有以换行符结尾的密钥文件。 - mob
ARGF对象在两个输入文件之间添加了一个换行符。我正在剥离它以匹配示例结果。如果这不是必需的,我将很乐意节省这10个字符。 - AShelly
1
此外,这会将输出放在标准输出上,您需要使用命令行魔法来替换输入。但是如果Golfscript可以做到这一点,我不明白为什么Ruby不能做到。 - AShelly
$><<只是在等待一个可爱的名字。 - Jordan Running

4

另一个 Perl 解决方案,59 (42) 个字符

(符合要求的一行代码,目前似乎可以正常工作:)

使用计算出的密钥长度的程序(59个字符):

 $.-1?$_^=substr($k x((length)/length($k)+1),0,length):$k=$_

如果使用mobrule的“脆弱”方法来确定密钥长度,则长度将为42个字符:

 $.-1?$_^=substr($k x 1e4,0,(length)):$k=$_

命令行:

 $> perl -i -0777 -pe'<insert above>' keyfile messagefile

这个操作将会把消息转换为它的异或形式,然后再转换回明文形式:

 $> cat keyfile ; cat messagefile

 Code Golf
 StackOverflow is Cool

应用命令:

 $> perl -i.bak -0777 -pe'<insert above>' keyfile messagefile
 $> cat keyfile ; cat messagefile

 Code Golf
 ^P^[^E^FK^H^Y   ^Tl/^@^SEI4O/   e/e

重新申请:
 $> perl -i.bak -0777 -pe'<insert above>' keyfile messagefile
 $> cat keyfile ; cat messagefile

 Code Golf
 StackOverflow is Cool

Regards

rbo


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接