Implemente una almohadilla de una sola vez

13

Antecedentes

Un pad de una sola vez es una forma de cifrado que se ha demostrado que es imposible de descifrar si se usa correctamente.

El cifrado se realiza tomando un texto sin formato (compuesto solo por letras AZ) y generando una cadena aleatoria en la misma longitud (también solo letras). Esta cadena actúa como la clave. Cada carácter en el texto sin formato se empareja con el carácter correspondiente en la clave. El texto cifrado se calcula de la siguiente manera: para cada par, ambos caracteres se convierten en números (A = 0, B = 1, ... Z = 25). Los dos números se suman módulo 26. Este número se convierte de nuevo en un carácter.

El descifrado es exactamente lo contrario. Los caracteres en el texto cifrado y la clave se emparejan y se convierten en números. La clave se resta del módulo 26 de texto cifrado y el resultado se convierte de nuevo en un carácter AZ.

El reto

Su desafío es escribir el programa más corto posible que pueda cifrar y descifrar una libreta de una sola vez.

En la primera línea de entrada (a STDIN), habrá la palabra "ENCRYPT" o la palabra "DECRYPT".

Si la palabra está encriptada, la siguiente línea será el texto sin formato. Su programa debe generar dos líneas (a STDOUT), la primera es la clave y la segunda el texto cifrado.

Si la palabra se descifra, su programa obtendrá dos líneas más de entrada. La primera línea será la clave, y la segunda línea será el texto cifrado. Su programa debe generar una línea, que será el texto sin formato que se ha descifrado.

El texto sin formato, el texto cifrado y la clave siempre deben consistir en letras mayúsculas AZ. Siempre serán una sola línea y no contendrán espacios en blanco.

La clave siempre debe ser aleatoria. No se deben repetir partes grandes entre las ejecuciones, y no debe haber patrones que se puedan encontrar en el texto.

Dos ejemplos simples:

ENCRYPT
HAPPYBIRTHDAY
>ABKJAQLRJESMG
>HBZYYRTICLVME

DECRYPT
ABKJAQLRJESMG
HBZYYRTICLVME
>HAPPYBIRTHDAY

El >representa lo que las líneas son de salida, por lo que no tiene que imprimir ese símbolo como salida.

PhiNotPi
fuente
77
No criticar el desafío por sus propios méritos (que están bien), pero estoy va a criticar a la criptografía de aquí. Lo que está describiendo es un "cifrado de flujo", ya que depende de un PRNG (a menos que su computadora tenga acceso a una fuente o aleatoriedad real (y si la implementación de Linux de / dev / urandom count es un tema de debate)), y tener la clave desarrollada en el momento de cifrar derrota el único uso realmente bueno para una OTP, que es el cambio de tiempo de las comunicaciones seguras.
dmckee --- ex-gatito moderador
1
Además, todos los desafíos son independientes del idioma de forma predeterminada, por lo que he eliminado esa etiqueta.
dmckee --- ex-gatito moderador
77
@dmckee Con respecto a su primer comentario, estoy de acuerdo, por lo que no tengo la intención de utilizar estas respuestas para asegurar mis comunicaciones.
PhiNotPi
1
Esto hubiera sido más divertido de la OMI para dejar la aleatoriedad fuera del problema; dada una fuente de aleatoriedad ( /dev/random, haveged), cifrar xorreando los ords con los bytes y descifrar xormentándolos con la clave. gist.github.com/5078264 la clave o la aleatoriedad se pueden leer desde stdin, el mensaje o el texto cifrado pueden ser un argumento de nombre de archivo.
ixtmixilix
@PhiNotPi Tengo una sugerencia. ¿Por qué no dar un bono si usan una fuente verdaderamente aleatoria (como /dev/hwrng, en lugar de usar pseudoaleatorio (lo que técnicamente hace que se rompa))
PyRulez

Respuestas:

8

GolfScript, 53 caracteres

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

Esta es una tarea para la que GolfScript parece estar perfectamente adaptado.

Para mantener el código corto, estoy usando el mismo código tanto para el cifrado como para el descifrado: para descifrar, resta la clave del texto cifrado, mientras que para el cifrado, primero genero un texto cifrado aleatorio y luego le resta el texto plano. Aun así, el código adicional para implementar el modo de encriptación toma un poco más de la mitad de la longitud del programa.

Versión de golf con comentarios:

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
fuente
4

Rubí ( 200 185)

ejecuciones de muestra + 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
fuente
s[k=(p=f).map{rand 26}],r[k,p,:-]debería escribirses[k=f.map{rand 26}],r[k,$_,:-]
Hauleth
@Hauleth no, eso no funciona, ya que $_es solo la última línea leída gets. fTambién lo hace .scan(/./).map{|b|b.ord-65}después de leer una línea.
jsvnm
3

Haskell, 203 caracteres

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

Ejemplo:

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

Perl, 220 171 caracteres

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}

Ejecución de muestra:

ENCRYPT
HELLO
CCTKK
JGEVY

DECRYPT
CCTKK
JGEVY
HELLO

Nota: al menos cuando lo ejecuto, "Presione cualquier tecla para continuar ..." se agrega al final de la última salida. Espero que esto esté bien, porque no es parte del programa. Si no, puedo hacerlo para que aparezca en la siguiente línea.

Este es mi primer programa real en Perl y mi primer golf, por lo que agradecería mucho los consejos. Además, encontré /(.)/gen Internet, pero no tengo idea de cómo funciona (¿es una expresión regular? Todavía no he aprendido esas). ¿Alguien me lo puede explicar?

EDITAR: ¡Gracias a Ilmari Karonen por ayudarme con las expresiones regulares, usé mi nuevo conocimiento para salvar a 7 personajes!

Versión ampliada, ligeramente legible:

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
}
comando
fuente
Sí, /(.)/ges una expresión regular. Definitivamente querrás aprender esos si vas a jugar al golf Perl. perldoc.perl.org/perlre.html no es un mal punto de partida.
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)

Creo que esto cumple exactamente con las especificaciones (incluidas las que se encuentran '>'al comienzo de las solicitudes de entrada). No valida la entrada, por lo que creo que solo producirá basura si le da caracteres fuera de [A-Z]. También solo verifica la primera letra del comando de entrada. Todo lo que comience Dresultará en un descifrado y cualquier otra cosa resultará en un cifrado.

Gordon Bailey
fuente
No esperaba que imprimiera el >, solo lo estaba usando para demostrar qué líneas salían. No tienes que implementarlos.
PhiNotPi
Ok, genial, 9 caracteres menos entonces.
Gordon Bailey
1

C ++ - 220 241 caracteres, 4 líneas

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

Editar 1- La biblioteca estándar de MSVS parece incluir muchos archivos innecesarios, lo que significa que iOS tenía todas las inclusiones que necesitaba, pero esto no funcionó con otros compiladores. Los ios modificados para los archivos reales que las funciones necesitadas aparecen en cstdlib y cstdio. Gracias a Ilmari Karonen por señalar esto.

Scott Logan
fuente
No compila para mí: g++ otp.cppdiceotp.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, eso es extraño, yo uso Visual Studio. Debe ser no estándar para <ios> tener <conio.h> y <stdio.h> en su incluye. Supuse que los encabezados siempre incluían los mismos archivos en diferentes implementaciones. Lo investigaré más tarde, gracias.
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

Salida de muestra:

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

Número de letras:

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

J: 94 bytes

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

Todo el espacio en blanco necesario contado.

Versión comentada:

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
fuente
1

C # ( 445 416)

Olvidé sobre Aggregate. Cortar un poco.

Algo de 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);
        }
    }
}

}

Golfizado:

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
fuente
0

C (159 + 11 para banderas del compilador)

Golfizado:

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

Sin golf:

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

Compilar con -Dg=gets(s) .

Ejemplo:

$./onetimepad
ENCRYPT
FOOBAR
>PHQGHU
>UVEHHL
$./onetimepad
DECRYPT
PHQGHU
UVEHHL
>FOOBAR
es1024
fuente
Recibo la misma clave cada vez que la ejecuto, no hay aleatoriedad.
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}

Uso:

var str = "HELLOWORLD";
var key = R(str.length);
var enc = X(str, key, false);
console.log(enc);
console.log(X(enc,key, true));
martillo de lobo
fuente
0

Rubí - 184 179 177 caracteres

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}*''

Ejecútelo así: $ ruby pad-lock.rb

Esta es la versión sin golf si alguien está interesado (aunque no está muy actualizada con la de 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
fuente