[PHP:rfc] 더 짧은 속성 구문

원문: https://wiki.php.net/rfc/shorter_attribute_syntax

PHP 8.0에선 많은 내용들이 추가되면서, rfc들도 많이 요청되면서 다양한 토론들이 이루어지고 있습니다.
해당 rfc는 2020/06/03 23:24에 최초로 생성된 rfc이며, 내부 검토를 마치고 토론중에 있습니다.

주장도 함리적이고, 내용들에 대해서 자세한 예시까지 해줘서 해당 rfc를 간단한게 정리해보았습니다.


PHP RFC: Shorter Attribute Syntax


Introduction

우리는 PHP 8.0에서 속성 구문을 <<Attr>> 대신 @@Attr을 사용할 것을 제안합니다.

저번달 초, Attributes v2는 PHP 8.0적용되는 것이 승인되었으며, 투표 동안 <<Attr>>@:Attr의 구문중에서 <<Attr>>구문이 3/4에게 선택 되었습니다.
하지만, 이 구문은 몇 가지 주목할만한 단점이 있습니다.

1. 장황함 (Verbosity)

아래의 RFC 예시에선 속성의 이름 보다 시프트 토큰(<<>>)에 사용되는 문자가 더 많습니다.

<<Jit>>
function foo() {}

특히 하나 이상의 속성이 필요할땐 이 구문은 한눈에 보기 어려울 수 있습니다.
이를 완화 하기위해  속성 수정 RFC에선 여러개의 속성에서 하나의 시프트 토큰을 사용하는 방법을 제안 했습니다.

<<Attr1("foo"),
  Attr2("bar")>>
public function test() {}

하지만, 이 방법은 다른 문제가 있습니다. 속성을 추가, 삭제하려면 여러줄을 수정되어 diff에 노이즈가 생기게 됩니다.
다만, 이 제한은 후행 쉼표를 허용해 다음과 같이 작성 하여 해결 할 수 있습니다.

<<
  Attr1("foo"),
  Attr2("bar"),
>>
public function test() {}

하지만, 스프트 토큰에 두개의 추가 행이 필요하며, 속성끼리 서로 이동할 때에 여러 줄이 수정되어지게 됩니다.

2. 중접 된 속성의 부재 (Lack of nested attributes)

중접된 주석은 Doctrine에서 많이 이용 되고 있으며, 여기서 그 예를 볼 수 있다.

@JoinTable(
    name="User_Group",
    joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
    inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
)

<<>>구문(및 잠재적 속성 그룹화)으로 속성을 중접하는 방법은 새로운 Attr 표현식을 허용하는 것입니다.
하지만, 구현하기 쉽지 않으며, 혼란을 야기할수 있습니다. (예시로 new Foo()는 허용되지만, Foo::create()는 작동되지 않습니다).
결국 이것은 const 표현식에 많은 변경이 필요하기에 구현 중 포기되었습니다.

3. 제너릭과의 혼동 (Confusion with generics)

속성과 제너릭의 구문간에 기술적으로 충돌은 없지만, PHP에서 제너릭이 지원되면 속성과 제너릭이 사용되는 위치를 한눈에 파악하기 더 여러울 수 있습니다.

4. 쉬프트 연산자와 const 인수와의 혼동 (Confusion with shift operators and const arguments)

시프트 토큰을 재사용 하면, 상수 표현식인지, 멀티 속성인지, 또는 쉬프트 토큰인지 식별하기 어려울 수 있습니다.

<<Bar(2 * (3 + 3)>>Baz, (4 + 5) * 2)>>
function foo() {}

물론 이 이런 상황에서의 쉬프트 토큰은 거의 사용되지 않으므로 마니어너한 문제입니다만,
만약 그룹화 된 속성 제한이 승인되면, 쉼표로 구분된 속성과 속성인수간의 유사한 혼동이 있을 수 있습니다.

<<Attr1(2 * 3 + 3), Bar(4 + 5 * 2)>>
<<Attr2(2 * (3 + 3), Baz, (4 + 5) * 2)>>
function foo() {}

5. 다른 언어와 유사하지 않음 (Dissimilarity to other languages)

대부분의 C 패밀리 언어들은 속성 구문에 [Attr] 또는 @Attr 를 사용하며ㅡ, 4개가 아닌 1~2개의 문자만 필요합니다. (아래의 비교 참고)


Proposal

PHP8.0에서 <<Attr>> 구문 대신에 @@Attr구문을 사용합시다.

그렇게하면 위의 문제가 해결되며, 속성이 있는 대부분읜 다른 언어와 비교하여 절반 정도의 문자만 필요하므로 장황함이 줄어들게 됩니다.

@@Jit
function foo() {}

@@구문은 충접된 속성과 충돌 하지않아, 원하는 경우 나중에 지원을 추가하는 것이 간단해집니다. (특이 케이스나 const 표션식을 변경할 필요없이)

@@JoinTable(
    "User_Group",
    @@JoinColumn("User_id", "id"),
    @@JoinColumn("Group_id", "id"),
)
private $groups;

더 짧은 구문은 제너릭 또는 스프트 토큰과 달리 속성이 사용되는 위치를 쉽게 알 수 있어 코드 가독성을 향상 시킬 수 있습니다.

@@Attr1("foo")
@@Attr2("bar") // 이 라인은 다른줄과 독립적으로 추가, 삭제할 수 있습니다.
public function test() {}

Discussion

왜 “@:” 구문을 반대 합니까?

우선 “@;”와 같은 실수로 오타가 발생하기 쉽다는 것입니다.
일부 사람들의 경우 비대칭적인 문법을 싫어 하기 때문이기에 PHP의 토큰들과 잘 맞지 않기 때문입니다.
@@문법은 위 두가지 사항을 모두 해결합니다.

왜 키워드로 대신 사용하지 않습니까?

기호 대신 키워드를 사용하는것은 좋습니다.

attribute Foo();
function myFunc() {}

하지만, 제안의 목적은 덜 일반적이고 다른 언어들에서 공통적으로 사용되는 속성 구문과 더 잘맞는 구문에 도달 하는 것입니다.
키워드를 사용하게되면 이러한 목표를 충족하지 못하게됩니다.

구문의 선택은 우리가 주관이 아닌 익숙한것으로 해야하지 않습니까?

어느정도 동의합니다. 하지만 이 RFC가 간략화 하려는 짧은 구문을 선호하는 객관적인 이유도 있을 것입니다.

다른 언어들과의 비교

속성이 있는 다른 언어들은 구문에 [Attr] 혹은 @Attr을 변형하여 사용합니다.
Hack은 <<Attr>>을 사용하는 유일한 언어이지만 PHP와의 호환을 지향하지 않기 때문에 @Attr로 이전 하는 것 같습니다.

  • C#: [Attr] 1
  • C++: [[attr]] 2
  • Hack: <<Attr>> 3 (하지만, @Attr로 변경 중에 있습니다.) 4
  • Java: @Attr 5
  • Kotlin: @Attr 6
  • Python: @attr 7
  • Rust: #[attr] 8
  • Swift: @attr 9
  • TypeScript/JS: @Attr 10

Backward Incompatible Changes (이전 버전과 호환되지 않는 변경점)

이론적으로 BC(하위호환) 오류가 적습니다.
이후 다중 오차 억제 연산자들은 추가적인 영향이 추가될 수 있다. (예: @@@@@really_suppress_me()). (의역)
하지만 이렇게 사용하는 것은 유용하지 않으며, 어느 곳에서도 사용되지 않을 것이다.


Vote

PHP 8.0에서 속성 구문으로 제안된 @@ 를 사용 하시겠습니까? Yes/No [현재(2020/06/04)는 토론중입니다]


References


내용이 재미 있어보이고 좋은 의견이여서 번역해 봤습니다만,
개인적인 의견으론 Hack처럼 @ 연산자를 버리고 @Attr로 가는것도 괜찮아 보이는것같습니다. ㅎㅎ

간단히 공부하는 OAuth

이번 프로젝트 중에 OAuth를 구현하는 프로젝트가 있어 늘 궁금했지만, 그리고 늘 해야한다고 생각만 했던 OAuth에 대해 공부한 내용을 최대한 간단하고 간결하게 이야기해보려고 한다.

OAuth 1.0 ? 2.0?

왜 다들 2.0 만 사용하는지 궁금했는데, 간단히 요약하면,
2.0은 Https에게 보안을 전가 하고 인증에만 중심을 두겠다는거고,
1.0은 http던 https던 ftp던 뭐던 상관없이 (독립적으로) 인증을 하기위해 직접 보안을 신경써서 전송하겠다는 거다.

그 외의 명칭이 다르거나 다소 차이점은 있지만 가장큰 차이점은 위에서 말한 전송 방식에 대한 보안을 어떻게 신경쓰느냐 인 것 같다.

OAuth2.0 그리고 지옥으로 가는 길 과 같은 2.0은 위험하다! 라는 이야기도 있지만, 그러면 구글이 아직 까지 쓰고 있지 않을테니 여기서 가장 위험하다고 말해지는 https가 아닌 http를 사용할 경우의 위험만 신경쓰고 OAuth2.0는 https 상에서 통신 한다는 것만을 잊지 말자.

통신 순서

늘 어느 글을 가나 나오는 통신 순서다.

OAuth2 Overview | OAuth2

라고 하는데 솔직히 이것만 봐도 그리고 설명을 쭉 봐도 이해가 잘 되지 않는다.
그래서 그냥 내 나름 대로 정리해 보려고 한다.

목표와 명칭 정리

나는 내가 운영하는 사이트에 페이스북 로그인을 넣으려고 한다.
그럴때 아래와 같은 명칭을 가진다.

Resource Owner: 로그인 하는 사람이다.
Client: 내가 운영하는 사이트다.
Resource Server: 페이스북이다.
Authorization Server: 인증서버인데. 그냥 Resource Server와 통합되있다고 생각하자.

1. OAuth 준비

먼저 난 페이스북 개발자 사이트에 가서 Client id와 Client secret key를 발급 받는다.

2. 로그인 페이지에 페이스북 로그인 버튼을 추가한다.

이제 처음 들어오는 유저가 페이스북 로그인을 클릭하고,

최초 접속이니 내 서버의 Client id와 함께 해당 유저를 페이스북 OAuth 인증 페이지로 리다이렉트 한다.

늘 보던 이런거

확인을 누르면 callback URL으로 임시 패스워드[Authorization code]를 전달한다.

3. 내 서버에서 Access Token 발급 요청

서버는 임시 패스워드와[Authorization code]
내 서버의 client id, secret key를 가지고 Access Token을 발급 요청을 한다.
(혹은 Refresh token도 함께 받는다) 그리고 저장한다.

4. 내 서버에서 Access Token을 사용해 사용자 데이터에 접근한다.

발급 받은 Access Token을 사용해 사용자에 데이터에 접근할 수 있다.
그 정보로 로그인 내 서버에 로그인 시켜주면된다.

다만, 일정 시간이 지나면 Access Token이 만료되기에 Refresh Token을 사용하여 재발급 받는다.

이때 Refresh Token은 영구적으로 안별할 수 도 있고, 재발급 받을 때 마다 Refresh Token도 재발급 될 수 있다. 이는 각 사이트 정책을 따른다.

+α 이미 정보가 저장 되있으면?

그럼 위 2번에서 바로 내 서버의 callback url로 임시 패스워드가[Authorization code] 보내지고 Access Token을 가지고 혹은 갱신해서 로그인 정보를 가지고 올 수 있다.

즉 확인 버튼 누르는 과정만 없어졋다고 생각 해도 된다.


여튼 여러가지 설명이 있는데 내가 이해한 방식은 위와 같은 방식이고,
OAuth를 이해 하는데 도움이 되었으면 좋겠다.

[phpunit] Skip Test with message

PHPunit을 사용해 테스트를 하다보면 추후 기능 구현을 위해 남겨 두었다가 개발을 위해 잠시 테스트를 스킵하고 싶은데,
이에 대한 걸 단순히 주석 처리나, ignore시켜서 하기에는 나중에 나 스스로를 못믿기도 하고(…)
테스트 할때마다 이를 표시하고 싶은데, risk 상태로 두자니 찝찝하고 여러모로 어떻게 처리해야할지 모르는 상황에서 markTestSkipped메소드를 찾게 되었다.

Mark Test Skipped

이 메소드는 이름 그대로 해당 test를 넘겨주는 역할을 한다. 다만, 테스트를 아에 건너 뛰는것이 아닌 해당 테스트가 스킵되었고 어떤 내용으로 스킵되었는지를 표시해준다.

// Test codes ...
    public function testSomeThing(): void
    {
        $this->markTestSkipped('this test skipped because i want');
        // ...
    }
// Test codes ...

위처럼 markTestSkipped는 문자열을 인수로 받는다. skip의 s 로 표시된다.

PHPUnit 8.5.4 by Sebastian Bergmann and contributors.

.S                                                  2 / 2 (100%)

Time: 37.1 seconds, Memory: 28.00 MB

OK, but incomplete, skipped, or risky tests!
Tests: 2, Assertions: 1, Skipped: 1.

markTestIncomplete 와의 차이점

또한 phpunit에는 markTestIncomplete메소드가 있어 해당 테스트가 완료되지 않았다는 것을 표기해 줄 수 있다. 위에 상황을 봣을 땐 이 메소드가 더 어울리는 것같지만, markTestSkippedartisan test를 이용할 때 다른 결과를 보여준다.

위처럼 markTestSkipped는 해당 인수는 아래와 같이 테스트 결과와 함께 표시되며, 인자로 전달된 메시지를 출력해준다. 이를 이용해 왜 해당 테스트가 skip되었는지를 명확하게 표시 해 줄 수 있다.

silnex@localhost:~/test$ artisan test

   WARN  Tests\Feature\MyTest
  ✓ others
  s some thing → this test skipped because i want

  Tests:  1 skipped, 1 passed
  Time:   0.10s
silnex@localhost:~/test$ artisan test

   WARN  Tests\Feature\MyTest
  ✓ others
  i some thing

  Tests:  1 incompleted, 1 passed
  Time:   0.11s

예를 들어 config(‘range’) 의 값이 10 미만일 때 만 skip하고 싶고 왜 스킵되었는 표시해주고 싶을때 사용할수 있다.

[PHP] Namespace의 범위

TL;DR

PHP의 Namespace의 범위는 include, require의 상관없이 해당 파일에서만 영향을 끼친다.

Namespace

PHP 5.3부터 추가된 같은 이름의 함수, 클래스가 있을 때 네임스페이스로 각각 격리하여 사용할 수 있게 해주는 기능이다.

Simple example

spaceA.php

<?php

namespace A;

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

function atest() { echo "spaceA atest function\n"; }

spaceB.php

<?php

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

function btest() { echo "spaceB btest function\n"; }

index.php

<?php

namespace INDEX;

require('spaceA.php');
require('spaceB.php');

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

btest();
atest();

php index.php로 php를 실행시키면 결과는 다음과 같다.

/home/silnex/test/spaceA.php > A
/home/silnex/test/spaceB.php > 
/home/silnex/test/index.php > INDEX
spaceB btest function
PHP Fatal error:  Uncaught Error: Call to undefined function INDEX\atest() in /home/silnex/test/index.php:11
Stack trace:
#0 {main}
  thrown in /home/silnex/test/index.php on line 11

require('spaceA.php') 의 결과가 출력된 1번째 줄을 보면
네임스페이스는 index.php에서 선언된 INDEX가 아닌 spaceA.php 파일에서 선언된 네임 스페이스다.

require('spaceB.php')의 결과가 출력된 2번째 줄을 보면
네임스페이스는 INDEX도 A도 아닌 아무 선언 안된 것을 볼 수 있다.
즉, 네임스페이스는 다른파일엔 영향을 끼치지 않는다는 걸 알 수 있다.

btest()의 실행 결과인 4번째 줄을 보면,
정상적으로 실행되고 있으나,
atest()의 경우 5번째줄 부터 표시된 에러가 나는것을 볼 수 있다.

이를 통해 함수가 받는 영향을 알 수 있는데,
기본적으로 Namespace가 없는 함수를 먼저 호출 하고, 만약 함수가 없다면,
현재 파일의 Namespace의 함수를 찾는 것을 확인 할 수 있다.

[Clipping] php-fpm slow log

서비스 응답속도 저하시 php-fpm 로그로 원인분석하기

원글: HeuJung

php-fpm 기반의 웹 서비스에서 알 수 없는 문제로 응답속도가 저하되는 현상이 발생 할 경우가 있다.

원인은 여러가지일 수 있다. 몇 가지를 꼽자면,

  1. DBMS의 SQL 응답속도 저하 (Slow Query)
  2. 네트워크 지연
  3. 서버 리소스 점유율 (CPU, RAM 등)
  4. Disk 입출력 문제

등이 있다.

하지만, 서버 리소스나 네트워크에 별 다른 문제가 없고, Slow Query 로그에도 별다른 이상징후가 없다면,
원인분석을 해야 하는데, 이 경우 Web Application에서 어느 로직에서 문제가 발생하는지 원인을 찾아야 한다.

개발환경이라면 Xdebug를 활용 수 있겠지만, 실 서비스 중인 서버에는 적용할 수 없는 방법이다.

이 때는 php-fpm의 slowlog를 찍어서 확인하는 방법이 있다.

php-fpm.conf 파일을 열어보면 아래와 같은 설정을 확인할 수 있다.

......
request_slowlog_timeout = 30s
slowlog = /var/log/php-fpm/slow.log
......

여기서 request_slowlog_timeout을 3~5s 정도로 낮추고, php-fpm 서비스를 재시작한다.

그 후 tail 명령으로 /var/log/php-fpm/slow.log 를 확인 후 응답속도가 느린 페이지에 접속을 계속 시도해보면 다음과 같은 로그가 찍힌다.

[10-Nov-2018 11:30:22]  [pool www] pid 8260
script_filename = ******************
[0x00007f1a47de79a8] fopen() ****.php:422
[0x00007f1a47de6500] +++ dump failed

이 로그에는 응답속도 저하의 원인이 되는 파일과 해당 function까지 추적해주기 때문에 원인을 금방 파악할 수 있다.

[SimpleDES] Simplified Data Encryption Standard-2

Simplified Data Encryption Standard (Simplified DES) Practice

Author silnex


이전 글에서 SDES에 대한 내용과 필요한 함수들에 대해서 알아 보았다.
이번엔 직접 Keygen, Encryption 및 Decryption을 해보며 SDES에 대해서 알아본다.

Key Generation

Key 1 0 1 0 0 0 0 0 1 0
P10 3 5 2 7 4 10 1 9 8 6
P8 6 3 7 4 8 5 10 9

위 함수와 Key를 사용해 subKey(k1,k2)를 생성한다.

먼저 10bit의 key인 1010000010를 함수 P10에 넣는다.
 ▶ P10(1010000010) ::= (1000001100)
P10의 결과 값을 5bit 씩 쪼개어 LS-1을 진행한 뒤 다시 하나로 합친다.
 ▶ LS-1(10000) = (00001)
        LS-1(01100) = (11000) ::= 00001 11000

subKey 1 Gen LS-1의 결과 값을 P8에 넣는다.
 ▶ P8(0000111000) ::= 10100100 (k1)

LS한 결과 값에 LS-2를 한번 더 적용한 뒤 하나로 합친다.
 ▶ LS-2(00001) = (00100)
        LS-2(11000) = (00011) ::= 00100 00011

subKey 2 Gen LS-2의 결과값을 P8에 넣는다.
 ▶ P8(0010000011) ::= 01000011 (k2)

K1 K2
10100100 01000011

Encryption

PlainText 0 1 1 0 1 1 0 1
IP 2 6 3 1 4 8 5 7
E/P 4 1 2 3 2 3 4 1
P4 2 4 3 1
IP-1 1 3 5 7 2 8 6
S0 1 0 3 2 S1 0 1 2 3
3 2 1 0 2 0 1 3
0 2 1 3 3 0 1 0
3 1 3 2 2 1 0 3

위 함수를 통해 fk를 2번 반복한다.

8bit의 PlainText를 함수 IP에 넣는다.
 ▶ IP(01101101) ::= 1110 0110
{START fk} IP의 결과 값 중 오른쪽4bit(0110)를 함수 E/P에 넣는다.
 ▶ E/P(0110) ::= 00111100
 E/P 결과 값에 subkey k1과 xor 연산을 한다.
 ▶ 00111100 ⊕ 10100100 ::= 1001 1000
k1과 Xor한 결과 값을 S-Box에 넣어 매치되는 행렬값을 구한 뒤 합친다.
 ▶ S0(11,00) = S0(3,0) = 11 (3) | S1(10,00) = S1(2,0) = 11 (3) ::= 1111
S-Box의 결과 값을 P4에 넣고 처음 IP의 결과 값 중 왼쪽4bit와 Xor 연산을한다.
 ▶ P4(1111) ::= 1111 
       1111 ⊕ 1110 ::= 0001 {END fk}
Xor의 결과 값과 처음 IP의 결과 값 중 오른쪽4bit의 순서를 바꾼다.
 ▶ SW(0001 0110) ::= {0110 0001}

SW값을 한번 더 함수 fk를 반복한다.
{START fk} ▶ E/P(0001) ::= 10000010
 ▶  10000010 ⊕ 01000011 ::= 1100 0001
 ▶ S0(10,10) = S0(2,2) = 01 (1) | S1(01,00) = S1(1,0) = 10 (2) ::= 0110
 ▶ P4(0110) ::= 1010
      1010 ⊕ 0110 ::= 1100 {END fk}
 ▶ >> {1100 0001}

두번의 fk에서 나온 결과 값을 IP-1에 넣는다.
 ▶ IP-1(1100 0001) = 01000110
이렇게 나온 결과 값 0 1 0 0 0 1 1 0가 PlainText(01101101)를 Key(1010000010)로 암호화한 CipherText(암호문)이 된다.


 

Decryption

 

CipherText 0 1 0 0 0 1 1 0
IP 2 6 3 1 4 8 5 7
E/P 4 1 2 3 2 3 4 1
P4 2 4 3 1
IP-1 1 3 5 7 2 8 6
S0 1 0 3 2 S1 0 1 2 3
3 2 1 0 2 0 1 3
0 2 1 3 3 0 1 0
3 1 3 2 2 1 0 3

위 함수를 통해 fk를 2번 반복하는데 Encryption과 과정은 동일하다.
하지만 이번엔 Decryption이기 때문에 fk에서 사용되는 key의 순서가 k2, k1순으로 사용되어야한다.

8bit의 CipherText를 함수 IP에 넣는다.
 ▶ IP(01000110) ::= 1100 0001
{START fk} IP의 결과 값 중 오른쪽4bit(0001)를 함수 E/P에 넣는다.
 ▶ E/P(0001) ::= 10000010
 E/P 결과 값에 subkey k1과 xor 연산을 한다.
 ▶ 10000010 ⊕ 01000011 ::= 1100 0001
k1과 Xor한 결과 값을 S-Box에 넣어 매치되는 행렬값을 구한 뒤 합친다.
 ▶ S0(10,10) = S0(2,2) = 01 (1) | S1(01,00) = S1(1,0) = 10 (2) ::= 0110
S-Box의 결과 값을 P4에 넣고 처음 IP의 결과 값 중 왼쪽4bit와 Xor 연산을한다.
 ▶ P4(0110) ::= 1010
       1010 ⊕ 1100 ::= 0110 {END fk}
Xor의 결과 값과 처음 IP의 결과 값 중 오른쪽4bit의 순서를 바꾼다.
 ▶ SW(0110 0001) ::= {0001 0110}

SW값을 한번 더 함수 fk를 반복한다.
{START fk} ▶ E/P(0110) ::= 00111100
 ▶ 00111100 ⊕ 10100100 ::= 1001 1000
 ▶ S0(11,00) = S0(3,0) = 11 (3) | S1(10,00) = S1(2,0) = 11 (3) ::= 1111
 ▶ P4(1111) ::= 1111
       1111 ⊕ 0001 ::= 1110 {END fk}
 ▶ >> {1110 0110}

두번의 fk에서 나온 결과 값을 IP-1에 넣는다.
 ▶ IP-1(1110 0110) = 01101101
이렇게 나온 결과 값 0 1 1 0 1 1 0 1가 CipherText(01000110)를 Key(1010000010)로 복호화한 PlainText(평문)이 된다.


이미지 출처 및 참고 자료

[SimpleDES] Simplified Data Encryption Standard

Simplified Data Encryption Standard (Simplified DES)

Author silnex


Introduce S-DES

TL; DR
바로 Example로 넘어가고 싶다면,
[SimpleDES] Simplified Data Encryption Standard Practice 로…

Padding Oracle Attack을 공부하기 위해 CBC에 대해서 공부하던 중 Block Cipher에 대해 자세히 알고 싶다는 생각이 들어,
지금은 사용되지 않지만 가장 유명한 암호화 방식인 Data Encryption Standard(DES)를 공부하게 되었다.
DES알고리즘을 학습해야하지만 일반적인 DES에서 사용되는 key인 56bit는 다소 복잡해 이해 하기가 어려워,
key가 10bit로 이루어진 Simplified DES를 통해 DES알고리즘에 대해서 학습해본다.

SDES는 기존 DES와 Key의 bit수 차이와 반복되는 Round 의 차이만 있을 뿐 알고리즘은 동일하며,
SDES의 확장을 통해 DES를 이해를 위해 만들어진 SDES인 만큼 DES의 56bit키가 왜 위험한지에 대해서도 충분이 이해 할 수 있다고 한다.

Analyze DES

SDES Scheme (위 그림에 오류가 있는데 keygen을 하는 부분에서 2번째 Shift는 Shift를 2번해주어야 한다.)

DES는 대칭키 암호이기 때문에 Key를 통해 subKey인 K1과 K2를 생성하는 Key Generation()과정, (PlainText를 Block단위로 쪼개어 암호화 하기위해서다. DES가 Block Cipher라 불리는 이유)
PlainText(평문)를 암호화하는 Encryption() 과정,
암호화된 CipherText(암호문)를 복호화 하는 Decryption()과정까지 총 3개의 과정이 필요하다.

이제 필요한 함수에(P10, P8, Shift, IP, IP-1, SW, fk, E/P, S-Box, P4 )대해서 알아보자.

Functions

Key Generation에서 사용되는 함수들

Keygen for Simplified DES

 

Function P10 

input length(or bit): 10
output length(or bit): 10

P10의 P는 Permutation(순열)의 약자로, 아래와 같은 함수로 표현된다.

P10(k1, k2, k3, k4, k5, k6, k7, k8, k9, k10) = (k3, k5, k2, k7, k4, k10, k1, k9, k8, k6)

수식으로 표현하니 이해하기 여러울 수 도있지만, 단순히 순서를 바꾸는 역할을 할 뿐이다.
예를들어 P10(1,2,3,4,5,6,7,9,0) = (3,5,2,7,4,10,1,9,8,6) 처럼 순서만 바꾸는 역할을 한다.
다만, P10에서 사용되는 바뀌는 순서에 대한 값은 임의의 값이 아닌 SDES에서 고정적인 값이다.

P10을 표로써 표현하면 아래와 같이 표현할 수 있다.

P10
3 5 2 7 4 10 1 9 8 6

 

Function P8 

input length(or bit): 10
output length(or bit): 8

또한 P10과 같은 의미이나 10개의 인자값을 받아 8개만 선택해 출력하는 함수이다.
수식으로 표현하면,

P8(k1, k2, k3, k4, k5, k6, k7, k8, k9, k10) = (k6, k3, k7, k4, k8, k5, k10, k9)

즉, 결과 값에 없는 k1, k2는 버려진다. 이를 표로 표현하면 아래와 같이 표현 할 수 있다.

P8
3 7 4 8 5 10 9

 

Function Shift

input length(or bit): 5
output length(or bit): 5

Shift는 BitShift연산자중에서 Left Shift를 의미하는데, 왼쪽으로 넘어간 bit는 오른쪽에서 나오게 된다.
즉, 10011(2)을 Shift 2번 이라고 한다면, 00111(2)이 되는것이다.
대게 줄여서 LS라고 하며 Shift횟수에 따라 한번이면 LS-1 두번이면 LS-2라고 한다.

수식으로 표현하자면,

LS-1(10110(2)) = 01101(2); LS-2(11011(2)) = 01111(2)

 

Encryption, Decryption에서 사용되는 함수들

XOR(⊕)은 따로 설명하지 않는다.

Simplified DES Encryption Detail

 

Function IP and IP-1

input length(or bit): 8
output length(or bit): 8

IP는 Initial and Final Permutations의 약자로, 처음과 끝에 순서를 바꾸어 주는 역할을 한다.
즉 P10 과 크게 다르지 않다. 다만 IP는 ‘역’인 IP-1 가 존재한다. 
수식으로 표현하면,

IP(k1, k2, k3, k4, k5, k6, k7, k8) = (k2, k6, k3, k1, k4, k8, k5, k7)
IP-1(k2, k6, k3, k1, k4, k8, k5, k7) = (k1, k2, k3, k4, k5, k6, k7, k8)

으로 표현된다. 즉, IP-1(IP(X)) = X란 뜻이다.
이를 간단히 표를 통해 표현하면 아래와 같다.

IP
2 6 3 1 4 8 5 7
IP-1
4 1 3 5 7 2 8 6

 

Function fk

input length(or bit): 8
output length(or bit): 8

fk에서 k는 keygen에서 생성된 key의 종류를 의미한다.
fk는 입력받은 8bit의 값을 4bit/4bit 으로 쪼개어 왼쪽bit/오른쪽bit라고 부르며 나누어 사용한다.

중요한건 오른쪽bit인데 순서데로 E/P를 거쳐 Xor후에 S0, S1로 나누어져 동작 하고,
이를 다시 P4의 넣어 출력후 왼쪽bit와 Xor한다.
말로 표현하니 이해가 힘들수도 있지만, DES에서 가장 핵심이기에 잘 알아두자.

 

Function E/P

input length(or bit): 4
output length(or bit): 8

E/P는 Expansion/Permutation의 약자로 말 그대로 확장하고 순서를 바꾼단 뜻이다.
이도 IP나 P10과 같이 순서를 바꾸는 것은 동일하지만 4자리의 bit를 8bit로 확장 한다는 점이다르다.

E/P를 수식으로 나타내면 다음과 같고,

E/P(k1, k2, k3, k4) = (k4, k1, k2, k3, k2, k3, k4, k1)

이를 표로 나타내면 아래와 같다.

E/P
4 1 2 3 2 3 4 1

 

Function S0 S1(S-Box)

S0, S1 각각 input length(or bit): 4
output length(or bit): 2

S0와 S1는 S-Box라고 불리며, 기존의 E/P, IP, P10과는 달리 행렬을 이용한 연산이지만,
그렇게 복잡한 연산은 아니다.
다만, 이를 위해선 고정된 S-box 행렬이 필요한데 두 행렬은 아래와 같다.

S0 1 0 3 2 S1 0 1 2 3
3 2 1 0 2 0 1 3
0 2 1 3 3 0 1 0
3 1 3 2 2 1 0 3

입력(4bit)에대하여 위의 행렬을 참고해 다음과 같은 연산을 진행한다.

Ex) input : 0101 1010(2) 
(구분을 위해 색을 칠했으며, E/P와 key을 xor한 8bit 입력을 4bit로 쪼개어 각각 대입하여 사용한다.)

S0(01(2),10(2)) S1(10(2),01(2)) => S0(1,2) S1(2,2) => 1, 1 => 01 01(2)

S-box를 수식으로 표현하면 다음과 같이 표현할 수 있으나 이해가 위 예제를 통해 이해가 힘들때 참고 정도로만 사용하면된다.

i = input; n = 0, 1;
Sn(i1,4,i2,3)

Function P4

input length(or bit): 4
output length(or bit): 4

P4는 기존의 IP P10과 동일하게 4자리의 bit를 순서를 바꾸어 주는 역할을 한다.
수식으로 표현하면 다음과 같고,

P4(k1, k2, k3, k4) = (k2, k4, k3, k1)

표로 표현하면 아래와 같다.

P4
2 4 3 1

 

Function SW

input length(or bit): 8
output length(or bit): 8

SW는 단순히 왼쪽 4bit와 오른쪽4bit를 바꾸어주는 역할을 한다.
간단한 과정이니 수식으로만 표현하겠다.

SW(k1, k2, k3, k4, k5, k6, k7, k8) = (k5, k6, k7, k8, k1, k2, k3, k4)


이로써 S DES에서 암호화, 복호화와 키 생성에 필요한 함수를 모두 알아보았다.
이를 통해 다음 글에선Key를 직접 생성해 보고 Encryption과 Decryption을 직접 해본다.

 


이미지 출처 및 참고 자료

http://homepage.smc.edu/morgan_david/vpn/C-SDES.pdf

 

[Day 1] 시스템 해킹?

귀찬으니 정리만 한다.


#include <stdio.h>

int main(){
    int i;
    for(i = 0; i < 10; i++){
        printf("Hello, World!\n");
    }
    return 0;
}

간단히 hello world를 10번 출력하는 예제이다.
gcc helloword.c 와같이 컴파일 하게되면 아시다 시피 바이너리로 변해 컴퓨터가 해석을 위해 변환 되게 된다.

이를 기계어로 변하게 하는데 이를 볼 수 있게 해주는 objdump명령어를 통해서 자세히 봐보자.
Command : objdump -D a.out | grep -A20 main.:

위와 같이 입력하면, main 함수의 부분에서 20줄을 보여주게 된다. 실제론 이보다 훠~얼~씬 길지만..

0000000000400526 <main>:
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
  40052a:       48 83 ec 10             sub    $0x10,%rsp
  40052e:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
  400535:       eb 0e                   jmp    400545 <main+0x1f>
  400537:       bf e4 05 40 00          mov    $0x4005e4,%edi
  40053c:       e8 bf fe ff ff          callq  400400 <puts@plt>
  400541:       83 45 fc 01             addl   $0x1,-0x4(%rbp)
  400545:       83 7d fc 09             cmpl   $0x9,-0x4(%rbp)
  400549:       7e ec                   jle    400537 <main+0x11>
  40054b:       b8 00 00 00 00          mov    $0x0,%eax
  400550:       c9                      leaveq
  400551:       c3                      retq
  400552:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  400559:       00 00 00
  40055c:       0f 1f 40 00             nopl   0x0(%rax)

0000000000400560 <__libc_csu_init>:
  400560:       41 57                   push   %r15
  400562:       41 56                   push   %r14

각각 열이 의미하는 바에 대해 설명하자면,
제일 처음의 400526:은 메모리 주소를 의미하고 2번째 열은 3~4번째의 어셈블리 코드를 의미한다.

 

++ rbp ebp?? rex ebx??

위 두개는 같은 것을 의미하며, 현재 실행중인 컴퓨터의 환경(x64 or x86)의 따라서 r(x64) 또는 e(x86)이 붙게 된다.

 

위에 코드는 AT&T 문법 인데, 이를 Intel 문법으로 바꾸기 위해선 objdump 에서 -M intel 옵션을 추가해주면 아래와 같은 코드를 볼 수 있다.

0000000000400526 <main>:
  400526:       55                      push   rbp
  400527:       48 89 e5                mov    rbp,rsp
  40052a:       48 83 ec 10             sub    rsp,0x10
  40052e:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  400535:       eb 0e                   jmp    400545 <main+0x1f>
  400537:       bf e4 05 40 00          mov    edi,0x4005e4
  40053c:       e8 bf fe ff ff          call   400400 <puts@plt>
  400541:       83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
  400545:       83 7d fc 09             cmp    DWORD PTR [rbp-0x4],0x9
  400549:       7e ec                   jle    400537 <main+0x11>
  40054b:       b8 00 00 00 00          mov    eax,0x0
  400550:       c9                      leave
  400551:       c3                      ret
  400552:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  400559:       00 00 00
  40055c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

0000000000400560 <__libc_csu_init>:
  400560:       41 57                   push   r15
  400562:       41 56                   push   r14

 

x86 Debugging

GDB라는 디버깅 도구를 이용해 프로세서 레지스터의 상태를 볼 수 있다.

Command : gdb -q ./a.out

Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x40052a
(gdb) run
Starting program: /home/silnex/hack/a.out

Breakpoint 1, 0x000000000040052a in main ()
(gdb) info registers
rax            0x400526 4195622
rbx            0x0      0
rcx            0x0      0
rdx            0x7ffffffde448   140737488217160
rsi            0x7ffffffde438   140737488217144
rdi            0x1      1
rbp            0x7ffffffde350   0x7ffffffde350
rsp            0x7ffffffde350   0x7ffffffde350
r8             0x4005d0 4195792
r9             0x7fffff410ab0   140737475840688
r10            0x846    2118
r11            0x7fffff050740   140737471907648
r12            0x400430 4195376
r13            0x7ffffffde430   140737488217136
r14            0x0      0
r15            0x0      0
rip            0x40052a 0x40052a <main+4>
eflags         0x246    [ PF ZF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) quit
A debugging session is active.

        Inferior 1 [process 565] will be killed.

Quit anyway? (y or n) y

중지점(breakpoint)이 main() 함수에 설정 되어 실행 되기전에  멈추었고, info registers를 통해 실행중에 사용중인 레지스터의 내용들을 볼 수 있다.

첫번째 부터 4개의 레지스터 (eax, ecx, edx, ebx)는 범용 레지스터이고 각각 누산기, 카운터, 데이터, 베이스 레스터라고 부른다.
[위에서는 rax, rcx, rdx, rbx 라고 나왔지만 이는 실행 중인 환경이 x64여서 r이 붙은것일 뿐 의미하는 바는 같다.]

그 다음부터 4개의 (esp, ebp, esi, edi)도 범용 레지스터 이다. 이 레지스터는 가끔 포인터(pointer)나 인덱스(index)라고 부르기도 한다.
각기 스택 포인터, 베이스 포인터, 근원지(source) 인덱스, 목적지(destination) 인덱스라고 불리운다.

위의 레지스터들은 프로그램을 실행하고 메모리를 관리하는데 쓰이는 포인터 이며 매우 중요하게 사용된다.

eip 레지스터는 현재 프로세서가 읽고 있는 명령 포인터 레지스터 이고, 당현히 디버깅시 매우 많이 사용되게 된다.
마지막으로 eflags레지스터는 비교와 메모리 분할을 위한 몇 비트의 플래그로 이루어져있다. 이에 대해선 나중에 알아보자.

 

어셈블리 언어

대게 인텔의 문법 어셈블리어를 많이 사용하므로 (앞으로도 많이쓰게될진 모르겟지만..) 디버깅시 사용하는 툴도 인텔 문법에 맞춰 사용하기 위해서
set disassembly-flavor intel이라고 입력해 역어셈블 표기를 인텔로 정할 수 도 있으며,
명령을 넣은.gdbinit파일을 생성해 GDB를 실행할 때 마다 자동으로 환경설정 되도록 할 수 도 있다.

 

인텔 어셈블리어는 다음과 같은 형식으로 이루어져 있다.
명령 <목적지>, <근원지>

목적지근원지는 레스터나 메모리 주소 값이 될 수 있고 명령은 보통 직관적인 연산기호 이다.
mov명령은 근원지에서 목적지로 값을 이동,
sub는 빼고, inc는 증가시킨다.

예를 들어 다음 명령은 esp에서 ebp로 이동시킨 후 8을 뺀 결과를 esp에 저장하라는 명령이다.
8048375:        89 e5            mov    ebp,esp
8048377:        83 ec 08         sub    esp,0x8

또한 실행의 흐름을 제어하는 명령도 있다.
cmp 명령은 값을 비교하는데 사용되고,
j로 시작하는 명령은 코드를 다음부분으로 점프하는데 사용된다.

만약 gdb에서 소스코드를 볼 수 있는 추가 정보를 포함시키려면, 컴파일 시 gcc -g 플레그를 넣으면 된다.

Reading symbols from a.out...done.
(gdb) list
1       #include <stdio.h>
2
3       int main(){
4           int i;
5           for(i = 0; i < 10; i++){
6               printf("Hello, World!\n");
7           }
8           return 0;
9       }
(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400526 <+0>:     push   rbp
   0x0000000000400527 <+1>:     mov    rbp,rsp
   0x000000000040052a <+4>:     sub    rsp,0x10
   0x000000000040052e <+8>:     mov    DWORD PTR [rbp-0x4],0x0
   0x0000000000400535 <+15>:    jmp    0x400545 <main+31>
   0x0000000000400537 <+17>:    mov    edi,0x4005e4
   0x000000000040053c <+22>:    call   0x400400 <puts@plt>
   0x0000000000400541 <+27>:    add    DWORD PTR [rbp-0x4],0x1
   0x0000000000400545 <+31>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x0000000000400549 <+35>:    jle    0x400537 <main+17>
   0x000000000040054b <+37>:    mov    eax,0x0
   0x0000000000400550 <+42>:    leave
   0x0000000000400551 <+43>:    ret
End of assembler dump.
(gdb) break main
Breakpoint 1 at 0x40052e: file firstprog.c, line 5.
(gdb) run
Starting program: /home/silnex/hack/a.out

Breakpoint 1, main () at firstprog.c:5
5           for(i = 0; i < 10; i++){
(gdb) info registers rip
rip            0x40052e 0x40052e <main+8>
(gdb)

중지점이 main() 함수의 시작 점있다. 이때 17번째 줄에 0x000000000040052e <+8>: mov DWORD PTR [rbp-0x4],0x0의 주소값과, 
info registers rip를 통해 얻은 주소값이 동일함을 볼 수 있다.

17번째 줄까지의 명령들을 함수 프롤로그(function prologue)라고 불린다.