代码高尔夫:异或加密

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个回答

3

Haskell,181个字符

在Haskell中进行golf时,I/O非常棘手,而二进制I/O更是如此。这个解决方案可能可以大大改进。请随意提出意见!

import Data.Bits
import Data.ByteString as B
u=unpack
g o[l,n]=o$pack$Prelude.zipWith xor(u n)(cycle$u l)
f x=mapM B.readFile x>>=g(B.writeFile$x!!1)
main=Prelude.getLine>>=f.words

使用方法:

$ ghc --make encrypt.hs
$ echo -n 'Code Golf' > key
$ echo -n 'StackOverflow is Cool' > message
$ echo 'key message' | ./encrypt
$ od -tx1 message

你不能通过去掉“Prelude”前缀来剥离一些字符吗? - fuz
@FUZxxl:不,这些函数与Data.ByteString中同名的函数冲突(该模块被设计为限定导入)。如果我没记错的话,只导入必要的函数反而更麻烦。 - Thomas
据我所知,如果你使用前缀导入它们,仍然可以不带前缀调用Prelude函数,这就是为什么存在“前缀导入”的原因(我真的不确定)。 - fuz
你可能会这样想,但不幸的是并非如此。至少在 GHC 6.12.1 中,它被拒绝为模棱两可的。 - Thomas

3

PowerShell,125 115个字符

目前为止,这似乎是基于.NET的最短答案:

$k=[char[]](gc $args[1]);$i=0;sc $args[0] ([byte[]]([char[]](gc $args[0])|%{$_ -bXor $k[$i++%$k.Length]})) -en byte

命令缩写已拼写的漂亮版本:

$k=[char[]](get-content $args[1])
$i=0
set-content `
   $args[0] `
   ([byte[]] ([char[]] (get-content $args[0]) `
              | foreach {$_ -bXor $k[$i++ % $k.Length]})) `
   -encoding byte

用法: powershell codegolf.ps1 message.txt key.txt。按照要求,它会覆盖 message.txt


1
除非你考虑使用IronPython,否则这是最短的.NET解决方案。你可以使用这个 ;) - Wayne Werner
感谢Giovanni通过邮件告知我一个错误:如果消息包含换行符,则此代码将无法正常工作,因为get-content在这种情况下不会返回一个字符串。 - Heinzi

3

q, 88个字符

这是使用Arthur Whitney编写的q语言实现的,灵感来源于APL和lisp。http://kx.com/

a[0]1:"x"$2 sv'{(x|y)&not x&y}.'0b vs''flip{y:count[x]#y;(x;y)}.(read1')a:(hsym')`$'.z.x

那么简单解释一下正在发生什么:(从右往左阅读)

a:(hsym')`$'.z.x

从运行时参数列表中创建两个文件句柄的列表,并将其保存在变量“a”中,以备后用。

(read1')

循环遍历这两个文件,读取它们并返回一个字节列表的列表,其中每个字节都等于0x00..0xFF ((22个字节),(10个字节))。

{y:count[x]#y;(x;y)}.

将密钥的长度调整为与消息相同。如果密钥过长,则被截断;如果太短,则会重复。现在,该列表已经格式化良好,为2x22。

flip

将列表转置,现在变成了22x2。

0b vs''

将列表中的每个元素转换为二进制类型

{(x|y)&not x&y}.'

对所有22个元素进行逐对异或操作,返回一个包含8个布尔值列表的列表。
"x"$2 sv'

将8个布尔位转换为字节。

a[0]1:

写文件,覆盖原始消息文件。

示例运行:

$ cp message.txt message.txt.bk
$ q g.q message.txt key.txt    
$ diff -s message.txt message.txt.bk0
Binary files message.txt and message.txt.bk0 differ
$ q g.q message.txt key.txt          
$ diff -s message.txt message.txt.bk0
Files message.txt and message.txt.bk0 are identical

2

Ruby - 158 chars

def a(b);File.readlines(b).join("\n").chomp;end;t=a($*[0]);k=a($*[1]);File.open($*[0],"w"){|f|0.upto(t.length-1){|i|f.putc((t[i]^k[i.modulo(k.length)]).chr)}}

更美观的版本:

def a(b)
    File.readlines(b).join("\n").chomp
end

t = a($*[0])
k = a($*[1])

File.open($*[0],"w") {|f|
    0.upto(t.length - 1) {|i|
        f.putc((t[i] ^ k[i.modulo(k.length)]).chr)
    }
}

这个解决方案利用了问题的以下方面:

如果您接受任务,您的使命就是用最少的按键次数创建一个程序,...

这个解决方案是使用手写识别输入在我的平板电脑上编写的。在创建这个代码时没有按键被敲击。因此,这个程序是在零按键下开发的。游戏结束,我赢了!

在这里有很多可以进行的小改进:a=proc{|b|...}相较于def a(b);...;end节省了2个字符。t,k=$*.map{|f|a(f)}需要减去2个字符。用File.new代替open需要减去1个字符。使用(t.size-1).times而不是upto...需要减去2个字符。 - AShelly
你为什么要拼写出modulo而不是使用%符号? - AShelly

2

Python - 127个字符

使用命令行参数来指定密钥文件和数据文件。

import sys
a=sys.argv
_,k,t=[open(x).read()for x in a]
s=open(a[2],"w").write
[s(chr(ord(x)^ord(y)))for x,y in zip(k*len(t),t)]

向标准输出写入 - 109个字符

import sys
_,k,t=[open(x).read()for x in sys.argv]
print"".join(chr(ord(x)^ord(y))for x,y in zip(k*len(t),t))

1

Java,336 316 405 字符

编辑:忘记了它必须从文件中读取。*叹气

public class A {
public static void main(String[] a) throws Throwable {
    char[] p = new BufferedReader(new FileReader(a[1])).readLine().toCharArray();
    char[] t = new BufferedReader(new FileReader(a[0])).readLine().toCharArray();
    int u = t.length;
    int k = 0;
    for (int i = 0; i < u; i++) {
        new FileOutputStream (a[0]).write((char) ((int) t[i] ^ (int) p[k]));
        k = k = ++k % p.length;
    }
}
}

值得一试。但我认为Java不是最好的语言...


k = k = ++k % p.length; 这个为什么不用 k=(k+1)%p.length 呢? - st0le
此外,您似乎没有在多个地方使用 u,因此直接使用 t.length 可以节省一些字符。 - st0le
对于(int i = 0; i < u; i ++){t [i] ^ = p [i%p.length];},然后将“write”移动到循环外。 - st0le
你可以将 int 声明缩短为 int u=t.length,k=0,i=0;。此外,所有这些强制转换都是不必要的。 - BalusC
+1 我喜欢字符计数增加的方式... 你为什么不去掉换行符,把所有的空格都压缩一下呢?这样可以更简洁。 - user295190

0

Python,154个字符

import sys,struct;_,f,k=sys.argv
open(f,'r+b').write(''.join(struct.pack('B',ord(a)^ord(b))for a,b in zip(open(f,'r+b').read(),open(k,'rb').read()*1000)))

0

F#,147 146个字符

这是基于driis的解决方案的。我所做的就是添加必要的缩进使其编译,交换命令行参数的顺序并将其紧凑化。虽然我认为它仍然可以缩短一点,但我不会感到惊讶。注意:您将收到有关不完整模式匹配的警告。通常情况下,我会第一个抱怨这一点,但我认为代码高尔夫应该例外于通常的最佳实践。 :)

open System.IO[<EntryPoint>]let m[|a;b|]=File.ReadAllBytes|>fun r->r a|>fun k->File.WriteAllBytes(b,Array.mapi(fun i->(^^^)k.[i%k.Length])(r b));0

F#,147个字符,更易读

open System.IO
let r=File.ReadAllBytes
[<EntryPoint>]
let m[|a;b|]=
 let k=r a
 File.WriteAllBytes(b,Array.mapi(fun i->(^^^)k.[i%k.Length])(r b));0

0

PHP,142 141个字符

编辑1:使用fputs()代替fwrite()

$t=fopen($argv[1],'r+');$s=fgets($t);rewind($t);$k=fgets(fopen($argv[2],'r'));for($i=0;$i<strlen($s);$i++)fputs($t,$s{$i}^$k{$i%strlen($k)});

美化输出:

$t = fopen($argv[1],'r+');
$s = fgets($t);
rewind($t);
$k = fgets(fopen($argv[2],'r'));
for($i=0; $i<strlen($s); $i++)
  fputs($t, $s{$i} ^ $k{$i % strlen($k)});

0

KSH93 - 152个字符

m=$(<$1)
k=$(<$2)
for ((e=0;e<${#m};e++)) 
do
out="$out$(printf "%02X" $(("'${m:$e:1}"^"'${k:${e}%${#k}:1}")))"
done
echo "${out}0d0a" | xxd -p -r >$1

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