· 

KeeLoqをVHDLで実装

Michrochipが採用している暗号ロジック。ひょんなことから、ちくと調べたりしたので記録を残す。GHDLで動かすところまで。進化したやつじゃなくレガシーなやつ。

参考ページ

wikipedia

Hosein HadipourさんのCによる実装

 仕様が超簡単。で、C言語よりもVHDLのほうが実装しやすいのでやってみる。

暗号化のブロック図(Wikipediaより)。

32bitの入力(上のブロック)を64bitの鍵を使いながら528回こねくり回して暗号化する。

VHDLで実装するにあたっては、クロックごとにシフトを実行し、NLFやらXORやらは組み合わせ回路として実装する。

復号化のブロック。

こちらも同じ方法でできそう。

で、さっそく。暗号化から。

keeloq_enc.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity keeloq_enc is
    port (
        i_pr : in std_logic_vector(31 downto 0);
        i_kr : in std_logic_vector(63 downto 0);
        o_busy : out std_logic;
        o_cr : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        i_clk : in std_logic
    );
end keeloq_enc;
 
architecture ppl_type of keeloq_enc is
    signal i_start_prev : std_logic:='0';
    signal i_start_pres : std_logic:='0';
    signal i_start_rising : std_logic:='0';
    signal indicator_busy : std_logic:='0';
    type t_state is (STATE_IDLE,STATE_RUN);
    signal state : t_state := STATE_IDLE;

    signal pr : std_logic_vector(31 downto 0):=X"00000000";
    signal kr : std_logic_vector(63 downto 0):=X"0000000000000000";
    signal nlf_arg : std_logic_vector(4 downto 0):="00000";
    signal nlf : std_logic:='0';
    constant nlf_table : std_logic_vector(31 downto 0):=X"3a5c742e";
    signal newz : std_logic:='0';
    signal cnt : integer range 0 to 1023:=0;
    constant cnt_max : integer range 0 to 1023:=528;
 
begin
    i_start_pres<=i_start;
    i_start_rising<='1' when (i_start_prev='0' and i_start_pres='1') else '0';
    o_busy<=indicator_busy;
    o_cr<=pr when indicator_busy='0' else X"00000000";

    nlf_arg<=pr(31)&pr(26)&pr(20)&pr(9)&pr(1);
    nlf<=nlf_table(CONV_INTEGER(nlf_arg));
    newz<=(nlf xor (pr(16) xor pr(0)) xor kr(0));
    
    process (i_clk,i_start_rising,state) begin
        if i_clk'event and i_clk='1' then
            i_start_prev<=i_start;
            if i_start_rising='1' and state=STATE_IDLE then
                pr<=i_pr;
                kr<=i_kr;
                indicator_busy<='1';
                state<=STATE_RUN;
                cnt<=0;
            end if;
            if state=STATE_RUN then
                if cnt<cnt_max then
                    pr<=newz&pr(31 downto 1);
                    kr<=kr(0)&kr(63 downto 1);
                    cnt<=cnt+1;
                else
                    indicator_busy<='0';
                    state<=STATE_IDLE;
                end if;
            end if;
        end if;
    end process;    
end;

keeloq_enc_tb.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity keeloq_enc_tb is
end keeloq_enc_tb;
 
architecture sim of keeloq_enc_tb is
    component keeloq_enc is port (
        i_pr : in std_logic_vector(31 downto 0);
        i_kr : in std_logic_vector(63 downto 0);
        o_busy : out std_logic;
        o_cr : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        i_clk : in std_logic
    ); end component;
    signal clksys : std_logic:='0';
    constant STEPSYS : TIME := 6.25 ns;
 
    signal i_pr_tb : std_logic_vector(31 downto 0):=X"f741e2db";
    signal i_kr_tb : std_logic_vector(63 downto 0):=X"5cec6701b79fd949";
    signal o_busy_tb : std_logic;
    signal o_cr_tb : std_logic_vector(31 downto 0);
    signal i_start_tb : std_logic:='0';

    signal countup_to_start : integer range 0 to 255:=0;
 
begin
    keeloq_enc_inst : component keeloq_enc port map(
        i_pr=>i_pr_tb,
        i_kr=>i_kr_tb,
        o_busy=>o_busy_tb,
        o_cr=>o_cr_tb,
        i_start=>i_start_tb,
        i_clk=>clksys
    );
    clksys <= not(clksys) after STEPSYS/2;
    process(clksys)begin
        if clksys'event and clksys='1' then
            if countup_to_start<30 then
                i_start_tb<='0';
                countup_to_start<=countup_to_start+1;
            elsif countup_to_start=30 then
                i_start_tb<='1';
                countup_to_start<=countup_to_start+1;
            else
                i_start_tb<='0';
            end if;
        end if;
    end process;
    process begin
        wait for 5 us;
        assert (false) report "Simulation End!" severity failure;
    end process;
end;

Makefile

VHDLC=/usr/bin/ghdl
SRC=keeloq_enc.vhd
TBSRC=keeloq_enc_tb.vhd
VCD=keeloq_enc.vcd
SIMRTL=keeloq_enc_tb
 
all :
        @make comp
        @make tb
        @make tb_e
        @make vcd
 
comp :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(SRC)
 
tb :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(TBSRC)
 
tb_e :
        $(VHDLC) -e --ieee=synopsys -fexplicit $(SIMRTL)
 
vcd :
        $(VHDLC) -r --ieee=synopsys -fexplicit $(SIMRTL) --vcd=$(VCD)

Hosein Hadipourさんのテストベクタの上のほうのやつと同じテストベクタにして、同じ結果になっています。

では、復号化。

keeloq_dec.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity keeloq_dec is
    port (
        i_cr : in std_logic_vector(31 downto 0);
        i_kr : in std_logic_vector(63 downto 0);
        o_busy : out std_logic;
        o_pr : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        i_clk : in std_logic
    );
end keeloq_dec;
 
architecture ppl_type of keeloq_dec is
    signal i_start_prev : std_logic:='0';
    signal i_start_pres : std_logic:='0';
    signal i_start_rising : std_logic:='0';
    signal indicator_busy : std_logic:='0';
    type t_state is (STATE_IDLE,STATE_RUN);
    signal state : t_state := STATE_IDLE;

    signal cr : std_logic_vector(31 downto 0):=X"00000000";
    signal kr : std_logic_vector(63 downto 0):=X"0000000000000000";
    signal nlf_arg : std_logic_vector(4 downto 0):="00000";
    signal nlf : std_logic:='0';
    constant nlf_table : std_logic_vector(31 downto 0):=X"3a5c742e";
    signal newz : std_logic:='0';
    signal cnt : integer range 0 to 1023:=0;
    constant cnt_max : integer range 0 to 1023:=528;
 
begin
    i_start_pres<=i_start;
    i_start_rising<='1' when (i_start_prev='0' and i_start_pres='1') else '0';
    o_busy<=indicator_busy;
    o_pr<=cr when indicator_busy='0' else X"00000000";

    nlf_arg<=cr(30)&cr(25)&cr(19)&cr(8)&cr(0);
    nlf<=nlf_table(CONV_INTEGER(nlf_arg));
    newz<=((nlf xor cr(31) xor kr(15)) xor cr(15));
    
    process (i_clk,i_start_rising,state) begin
        if i_clk'event and i_clk='1' then
            i_start_prev<=i_start;
            if i_start_rising='1' and state=STATE_IDLE then
                cr<=i_cr;
                kr<=i_kr;
                indicator_busy<='1';
                state<=STATE_RUN;
                cnt<=0;
            end if;
            if state=STATE_RUN then
                if cnt<cnt_max then
                    cr<=cr(30 downto 0)&newz;
                    kr<=kr(62 downto 0)&kr(63);
                    cnt<=cnt+1;
                else
                    indicator_busy<='0';
                    state<=STATE_IDLE;
                end if;
            end if;
        end if;
    end process;    
end;

keeloq_dec_tb.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity keeloq_dec_tb is
end keeloq_dec_tb;
 
architecture sim of keeloq_dec_tb is
    component keeloq_dec is port (
        i_cr : in std_logic_vector(31 downto 0);
        i_kr : in std_logic_vector(63 downto 0);
        o_busy : out std_logic;
        o_pr : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        i_clk : in std_logic
    ); end component;
    signal clksys : std_logic:='0';
    constant STEPSYS : TIME := 6.25 ns;
 
    signal i_cr_tb : std_logic_vector(31 downto 0):=X"e44f4cdf";
    signal i_kr_tb : std_logic_vector(63 downto 0):=X"5cec6701b79fd949";
    signal o_busy_tb : std_logic;
    signal o_pr_tb : std_logic_vector(31 downto 0);
    signal i_start_tb : std_logic:='0';

    signal countup_to_start : integer range 0 to 255:=0;
 
begin
    keeloq_dec_inst : component keeloq_dec port map(
        i_cr=>i_cr_tb,
        i_kr=>i_kr_tb,
        o_busy=>o_busy_tb,
        o_pr=>o_pr_tb,
        i_start=>i_start_tb,
        i_clk=>clksys
    );
    clksys <= not(clksys) after STEPSYS/2;
    process(clksys)begin
        if clksys'event and clksys='1' then
            if countup_to_start<30 then
                i_start_tb<='0';
                countup_to_start<=countup_to_start+1;
            elsif countup_to_start=30 then
                i_start_tb<='1';
                countup_to_start<=countup_to_start+1;
            else
                i_start_tb<='0';
            end if;
        end if;
    end process;
    process begin
        wait for 5 us;
        assert (false) report "Simulation End!" severity failure;
    end process;
end;

Makefileは省略。

はい。ちゃんと元の値に復号できています。

で、このアルゴリズムですが、FPGAを160MHzで動かして3.4usかかるわけです。

暗号文と平文が1組わかっているとして総当たりで秘密鍵を割り出そうとします。そすと、

3.4e-6*(2^64)秒=200万年

かかります。まぁ総当たりでの攻撃は鍵の長さとラウンド数に依存するので、こんな簡単なアルゴリズムでも鍵を割り出すのは困難です。世の中にはいろんな方法を考える人がいて、このレガシーな暗号生成アルゴリズムはすでに、、、

ひさしぶりの更新。いやー完全にさぼりです。なんかやる気起きんのよねー。