Terapkan Pad Sekali Kali

13

Latar Belakang

Sebuah satu kali pad adalah bentuk enkripsi yang telah terbukti tidak mungkin untuk memecahkan jika digunakan dengan benar.

Enkripsi dilakukan dengan mengambil plaintext (hanya terdiri dari huruf AZ) dan menghasilkan string acak dengan panjang yang sama (juga hanya huruf). String ini bertindak sebagai kunci. Setiap karakter dalam teks plaint kemudian dipasangkan dengan karakter yang sesuai di kunci. Ciphertext dihitung sebagai berikut: Untuk setiap pasangan, kedua karakter dikonversi menjadi angka (A = 0, B = 1, ... Z = 25). Dua angka ditambahkan modulo 26. Angka ini dikonversi kembali menjadi karakter.

Dekripsi justru sebaliknya. Karakter dalam ciphertext dan kunci dipasangkan dan dikonversi menjadi angka. Kuncinya kemudian dikurangi dari modulo 26 ciphertext, dan hasilnya diubah kembali menjadi karakter AZ.

Tantangan

Tantangan Anda adalah menulis program sesingkat mungkin yang bisa mengenkripsi dan mendekripsi pad sekali pakai.

Pada baris input pertama (ke STDIN), akan ada kata "ENCRYPT" atau kata "DECRYPT".

Jika kata itu dienkripsi, maka baris berikutnya akan menjadi teks biasa. Program Anda harus menampilkan dua baris (ke STDOUT), yang pertama adalah kunci, dan yang kedua adalah ciphertext.

Jika kata tersebut didekripsi, program Anda akan mendapatkan dua baris input lagi. Baris pertama akan menjadi kunci, dan baris kedua akan menjadi ciphertext. Program Anda harus menampilkan satu baris, yang akan menjadi teks biasa yang telah didekripsi.

Plaintext, ciphertext, dan key harus selalu terdiri dari huruf besar AZ. Mereka akan selalu menjadi satu baris dan tidak mengandung spasi.

Kuncinya harus selalu acak. Tidak ada bagian besar yang harus diulang di antara run, dan seharusnya tidak ada pola yang dapat ditemukan dalam teks.

Dua contoh sederhana:

ENCRYPT
HAPPYBIRTHDAY
>ABKJAQLRJESMG
>HBZYYRTICLVME

DECRYPT
ABKJAQLRJESMG
HBZYYRTICLVME
>HAPPYBIRTHDAY

The >mewakili yang garis adalah output, sehingga Anda tidak harus mencetak simbol sebagai output.

PhiNotPi
sumber
7
Tidak mengkritik tantangan pada itu kemampuannya sendiri (yang baik-baik saja), tapi saya saya akan mengkritik kriptografi di sini. Apa yang Anda gambarkan adalah "stream cipher" karena tergantung pada PRNG (kecuali komputer Anda memiliki akses ke sumber atau keacakan nyata (dan jika implementasi linux dari jumlah / dev / urandom adalah masalah perdebatan)), dan memiliki kunci yang dikembangkan pada waktu pengkodean mengalahkan satu-satunya penggunaan yang benar-benar baik untuk OTP yang merupakan pergeseran waktu komunikasi yang aman.
dmckee --- mantan moderator kucing
1
Selain itu, semua tantangan adalah agnostik bahasa secara default, jadi saya telah menghapus tag itu.
dmckee --- mantan moderator kucing
7
@ dmckee Mengenai komentar pertama Anda, saya setuju, itulah sebabnya saya tidak bermaksud menggunakan jawaban ini untuk mengamankan komunikasi saya.
PhiNotPi
1
ini akan menjadi IMO yang lebih menyenangkan untuk menghilangkan keacakan dari masalah; diberi sumber keacakan ( /dev/random, haveged), mengenkripsi dengan xoring ords dengan byte dan mendekripsi dengan xoring mereka dengan kunci. gist.github.com/5078264 kunci atau keacakan dapat dibaca dari stdin, pesan atau cyphertext dapat menjadi argumen nama file.
ixtmixilix
@PhiNotPi Saya punya saran. Mengapa tidak memberikan bonus jika mereka menggunakan sumber yang benar-benar acak (seperti /dev/hwrng, alih-alih menggunakan pseudo acak (yang secara teknis membuatnya rusak.)
PyRulez

Jawaban:

8

GolfScript, 53 karakter

n%(0=2%{~.,[{26rand 65+}*]:K]}*zip{{-}*~)26%65+}%K]n*

Ini adalah tugas yang sepertinya cocok dengan GolfScript.

Untuk menjaga agar kode tetap pendek, saya menggunakan kode yang sama untuk enkripsi dan dekripsi: untuk mendekripsi, saya kurangi kunci dari ciphertext, sedangkan untuk enkripsi, saya pertama-tama membuat ciphertext acak dan kemudian mengurangi plaintext darinya. Meski begitu, kode tambahan untuk menerapkan mode enkripsi hanya membutuhkan sedikit lebih dari setengah panjang program.

Versi de-golf dengan komentar:

n %             # split input into an array of lines

# KEY GENERATION FOR ENCRYPTION MODE:
(               # extract the first line from the array
0 = 2 %         # check if the first char of that line is odd (E = 69)...
{               # ...and execute this block if it is:
    ~           # dump the remaining lines (of which the should be only one) on the stack
    . ,         # calculate the length of the last line...
    [ { 26 rand 65 + } * ]  # ...make an array of that many random letters...
    :K          # ...and assign it to K
    ]           # collect all the lines, including K, back into an array
} *

# ENCRYPTION / DECRYPTION ROUTINE:
zip             # transpose the array of 2 n-char strings into n 2-char strings...
{               # ...and execute this block for each 2-char string:
    {-} *       # subtract the second char code from the first
    ~ )         # negate the result (using the two's complement trick -x = ~x+1)
    26 % 65 +   # reduce modulo 26 and add 65 = A
} %

# OUTPUT:
K ] n*         # join the result and K (if defined) with a newline, stringifying them
Ilmari Karonen
sumber
4

Ruby ( 200 185)

sampel berjalan + wc:

$ ruby onetimepad.rb
ENCODE
ANOTHERTESTINPUTZZZ
ZYCLGHDWLDASFUTHWKC
BPMIBXOXTPTQIVBMDPX
$ ruby onetimepad.rb
DECODE
ZYCLGHDWLDASFUTHWKC
BPMIBXOXTPTQIVBMDPX
ANOTHERTESTINPUTZZZ
$ wc onetimepad.rb
       4       7     185 onetimepad.rb
def f;gets.scan(/./).map{|b|b.ord-65};end
s=->a{a.map{|b|(b+65).chr}*''}
r=->b,a,o{s[a.zip(b).map{|a,b|(a.send o,b)%26}]}
puts(gets=~/^D/?r[f,f,:+]:[s[k=(p=f).map{rand 26}],r[k,p,:-]])
jsvnm
sumber
s[k=(p=f).map{rand 26}],r[k,p,:-]harus dituliss[k=f.map{rand 26}],r[k,$_,:-]
Hauleth
@Hauleth no itu tidak berfungsi, seperti $_yang baru saja dibaca oleh baris terakhir gets. fjuga tidak .scan(/./).map{|b|b.ord-65}setelah membaca satu baris.
jsvnm
3

Haskell, 203 karakter

import Random
main=newStdGen>>=interact.(unlines.).(.lines).f.randomRs('A','Z')
f k['E':_,x]=[z const k x,z(e(+))k x]
f _[_,k,x]=[z(e(-))k x]
e(%)k x=toEnum$65+o x%o k`mod`26
o c=fromEnum c-65;z=zipWith

Contoh:

$ runghc OneTimePad.hs <<< $'ENCRYPT\nHELLOWORLD'
QMNQKGFZFD
XQYBYCTQQG
$ runghc OneTimePad.hs <<< $'DECRYPT\nQMNQKGFZFD\nXQYBYCTQQG'
HELLOWORLD
hammar
sumber
3

Perl, 220 171 karakter

if(<>=~/D/){$_=<>;$w=<>;print chr((ord(substr$w,$i++,1)-ord$1)%26+65)while/(.)/g}else{$_=<>;$c.=chr((ord($1)-65+($i=rand(26)))%26+65),print chr$i+65while/(.)/g;print$/.$c}

Contoh Run:

ENCRYPT
HELLO
CCTKK
JGEVY

DECRYPT
CCTKK
JGEVY
HELLO

Catatan: setidaknya ketika saya menjalankannya, "Tekan sembarang tombol untuk melanjutkan ..." ditambahkan ke akhir output terakhir. Saya harap ini baik-baik saja, karena ini bukan bagian dari program. Jika tidak, saya bisa membuatnya sehingga muncul di baris berikutnya.

Ini adalah program nyata pertama saya di Perl, dan golf pertama saya, jadi saya akan sangat menghargai tips. Saya juga menemukan/(.)/g di internet, tetapi saya tidak tahu cara kerjanya (apakah ini ungkapan reguler? Saya belum mempelajarinya). Adakah yang bisa menjelaskannya kepada saya?

EDIT: Terima kasih kepada Ilmari Karonen karena membantu saya dengan regexps, saya menggunakan pengetahuan baru saya untuk menghemat 7 karakter!

Versi yang diperluas dan sedikit terbaca:

if(<>=~/D/){
    $_=<>;
    $w=<>;
    print chr((ord(substr$w,$i++,1)-ord$1)%26+65)while/(.)/g
}
else{
    $_=<>;
    $c.=chr((ord($1)-65+($i=rand(26)))%26+65),print chr$i+65while/(.)/g;
    print$/.$c
}
komando
sumber
Ya, /(.)/gadalah regexp. Anda pasti ingin belajar itu jika Anda akan bermain golf Perl. perldoc.perl.org/perlre.html bukan tempat awal yang buruk.
Ilmari Karonen
2

Python - 304 295

import random
r=raw_input
R=lambda s:range(len(s))
o=lambda c:ord(c)-65
j=''.join
if r()[0]=='D':
 s=r()
 d=r()
 print j(chr((o(s[i])-o(d[i]))%26+65)for i in R(s))
else:
 s=r()
 d=[random.randint(0,26)for i in R(s)]
 print j(chr((o(s[i])+d[i])%26+65)for i in R(s))
 print j(chr(n+65)for n in d)

Saya percaya bahwa ini memenuhi spesifikasi persis (Termasuk '>'di awal input prompt.) Ini tidak memvalidasi input, jadi saya pikir itu hanya akan menghasilkan output sampah jika Anda memberikan karakter di luar [A-Z]. Itu juga hanya memeriksa huruf pertama dari perintah input. Apa pun yang dimulai dengan Dakan menghasilkan dekripsi dan apa pun juga akan menghasilkan enkripsi.

Gordon Bailey
sumber
Saya tidak mengharapkan Anda untuk mencetak >, saya hanya menggunakannya untuk menunjukkan jalur mana yang dihasilkan. Anda tidak harus mengimplementasikannya.
PhiNotPi
Oke, keren, 9 karakter lebih sedikit.
Gordon Bailey
1

C ++ - 220 241 karakter, 4 baris

#include<cstdlib>
#include<cstdio>
#define a scanf("%s"
char i,s[99],t[99];int main(){a,t);a,s);if(t[0]>68){for(;s[i];++i)s[i]=(s[i]+(t[i]=rand()%26+65))%26+65;puts(t);}else for(a,t);s[i];++i){s[i]=65+t[i]-s[i];if(s[i]<65)s[i]+=26;}puts(s);}

Sunting 1- Perpustakaan standar MSVS tampaknya menyertakan banyak file yang tidak perlu yang berarti bahwa ios memiliki semua yang saya butuhkan tetapi ini tidak bekerja dengan kompiler lain. Mengubah ios untuk file aktual yang dibutuhkan fungsi muncul di cstdlib dan cstdio. Terima kasih kepada Ilmari Karonen karena menunjukkan ini.

Scott Logan
sumber
Tidak mengkompilasi untuk saya: g++ otp.cppkataotp.cpp: In function ‘int main()’: otp.cpp:3: error: ‘scanf’ was not declared in this scope otp.cpp:3: error: ‘rand’ was not declared in this scope otp.cpp:3: error: ‘puts’ was not declared in this scope otp.cpp:3: error: ‘puts’ was not declared in this scope
Ilmari Karonen
Huh, itu aneh, saya menggunakan studio visual. Itu harus non-standar untuk <ios> untuk memiliki <conio.h> dan <stdio.h> di dalamnya. Saya berasumsi header selalu menyertakan file yang sama pada implementasi yang berbeda. Saya akan memeriksanya nanti, Terima kasih.
Scott Logan
1

Python - 270

import random
i=raw_input  
m=i()
a=i()
r=range(len(a))
o=ord
j=''.join
if m=='ENCRYPT':
  k=j(chr(65+random.randint(0,25)) for x in r)
  R=k+"\n"+j(chr((o(a[x])+o(k[x]))%26+65) for x in r)
elif m=='DECRYPT':
  k=i()
  R=j(chr((o(k[x])-o(a[x]))%26+65) for x in r)
print R

Output sampel:

$ python onetimepad.py 
ENCRYPT
HELLOWORLD
UXCYNPXNNV
BBNJBLLEYY
$ python onetimepad.py 
DECRYPT
UXCYNPXNNV
BBNJBLLEYY
HELLOWORLD

Jumlah karakter:

$ wc -c onetimepad.py 
270 onetimepad.py
tomcant
sumber
1

J: 94 byte

3 :0]1
c=:(26&|@)(&.(65-~a.&i.))
r=:1!:1@1:
((],:+c)[:u:65+[:?26$~#)@r`(r-c r)@.('D'={.)r 1
)

Semua ruang putih yang diperlukan dihitung.

Versi yang dikomentari:

3 :0]1                                          NB. Make a function and call it
c=:(26&|@)(&.(65-~a.&i.))                       NB. Adverb for operating on the alphabet
                                                NB. (used for adding and subtracting the pad)
r=:1!:1@1:                                      NB. Read input line and decide (right to left)
((],:+c)[:u:65+[:?26$~#)@r   ` (r-c r)            @. ('D'={.)r 1
NB. Encryption (ger    0)    | Decryption (ger 1)| Agenda               
NB. pad,:(crypt=:plain + pad)| crypt - pad       | If D is first input, do (ger 1), else do (ger 0)
)
jpjacobs
sumber
1

C # ( 445 416)

Lupa tentang Agregat. Potong sedikit.

Sedikit golf:

namespace G {
using System;
using System.Linq;
using x = System.Console;
class P {
    static void Main() {
        string p = "", c = "", k = "";
        Random r = new Random();
        int i = 0;
        if (x.ReadLine()[0] == 'E') {
            p = x.ReadLine();
            k=p.Aggregate(k,(l,_)=>l+(char)r.Next(65,90));
            c=p.Aggregate(c,(m,l)=>m+(char)((l+k[i++])%26+65));
            x.WriteLine(k + "\n" + c);
        } else {
            k = x.ReadLine();
            c = x.ReadLine();
            p=c.Aggregate(p,(l,a)=>l+(char)((a-k[i++]+26)%26+65));
            x.WriteLine(p);
        }
    }
}

}

Golf:

namespace G{using System;using System.Linq;using x=System.Console;class P{static void Main(){string p="",c="",k="";Random r=new Random();int i=0;if (x.ReadLine()[0]=='E'){p=x.ReadLine();k=p.Aggregate(k,(l,_)=>l+(char)r.Next(65,90));c=p.Aggregate(c,(m,l)=>m+(char)((l+k[i++])%26+65));x.WriteLine(k+"\n"+c);}else{k=x.ReadLine();c=x.ReadLine();p=c.Aggregate(p,(l,a)=>l+(char)((a-k[i++]+26)%26+65));x.WriteLine(p);}}}}
Farami
sumber
0

C (159 + 11 untuk flag compiler)

Golf:

d(a,b){return(a+b+26)%26+65;}a;char s[999],b,*c=s-1;main(){g;a=*s-69;g;while(*++c)a?b=-*c,*c=getchar():putchar(b=rand()%26+65),*c=d(*c,b);a||puts("");puts(s);}

Tidak Disatukan:

d(a,b){
    //*a = (*a + b - 2*65 + 26) % 26 + 65; 
    return (a + b + 26) % 26 + 65;
}
a; char s[999], b, *c = s-1;
main(){
    gets(s);
    a = *s - 69; // -1 if decrypt 0 if encrypt
    gets(s);
    while(*++c){
        if(!a)
            putchar(b = rand() % 26 + 65); // 'A'
        else
            b = -*c, *c = getchar();
        *c = d(*c,b);
    }
    if(!a) puts("");
    puts(s);
}

Kompilasi dengan -Dg=gets(s).

Contoh:

$./onetimepad
ENCRYPT
FOOBAR
>PHQGHU
>UVEHHL
$./onetimepad
DECRYPT
PHQGHU
UVEHHL
>FOOBAR
es1024
sumber
Saya mendapatkan kunci yang sama setiap kali saya menjalankannya - tidak ada keacakan.
feersum
0

JavaScript 239

var F=String.fromCharCode
function R(l){var k='';while(l--)k+=F(~~(Math.random()*26)+65);return k}
function X(s,k,d){var o='',i=0,a,b,c
while(i<s.length)a=s.charCodeAt(i)-65,b=k.charCodeAt(i++)-65,c=d?26+(a-b):a+b,o+=F((c%26)+65)
return o}

Pemakaian:

var str = "HELLOWORLD";
var key = R(str.length);
var enc = X(str, key, false);
console.log(enc);
console.log(X(enc,key, true));
wolfhammer
sumber
0

Ruby - 184 179 177 karakter

def g;gets.scan(/./).map{|c|c.ord-65}end
m,=g
k=(s=g).map{rand 26}
m==4?(puts k.map{|c|(c+65).chr}*'';y=:+):(k,s=s,g)
puts s.zip(k).map{|c,o|(c.send(y||:-,o).to_i%26+65).chr}*''

Jalankan seperti ini: $ ruby pad-lock.rb

Ini adalah versi yang ungolfed jika ada yang tertarik (meskipun tidak cukup dengan versi golf)

def prompt
    gets.scan(/./).map{ |c|c.ord - 65 }
end

mode = prompt[0]
operator = :-
secret = prompt
key = secret.map { |char| rand(26) }

if mode == 4 # the letter E, or ENCRYPT
    key.map { |char| print (char + 65).chr }
    puts
    operator = :+
else
    # make the old secret the new key,
    # and get a new secret (that has been encrypted)
    key, secret = secret, prompt
end

chars = secret.zip(key).map do |secret_char, key_char|

    # if mode == 4 (E) then add, otherwise subtract
    i = secret_char.send(operator, key_char).to_i

    ((i % 26) + 65).chr
end

puts chars.join("")
addison
sumber