라이프로그


Java Regular Expression Package 의 버그 프로그래밍

가금 서버가 행걸려서 CPU 를 100% 다 잡아먹고 서비스가 안되는 현상이 종종 발생 하였는데

오늘 지인의 도움에 힘입어 jmap 과 jhat 을 이용해서 JVM 의 Heap 을 디버깅 해본 결과

스팸 차단을 위해 패턴에 등록한 regular expression 이 특정 검색 문자열을 만나면 라이브러리 내부에서 무한루프를 도는 현상을 찾았습니다.

물론 regular expression 자체가 좀 난해하게 만들어진 점이 없쟎아 있지만, 무한루프를 도는건 아무래도 문제가 있는 듯 싶네요, 유닉스에 있는 awk, grep 에서는 별 문제 없이 동작하더군요

오늘 잡힌 regular expression 과 입력 문자열은 다음과 같습니다.


------------------------------------------------------------------------------
- 정규식 : [wW]{0,3}\.*.*\.*.*\.[cC][^a-zA-Z0-9]{1,32}[nN]
- 문자열 : 니마........{엄청길게썼음972개}........나...... 안된다~^.^
------------------------------------------------------------------------------
- 정규식: [wW]{0,3}\.*.*\.*.*\.[cC]*%63[nN]*
- 문자열: 나........{엄청길게썼음928개}.........내일 사랑니수술 하러가..ㅠ.ㅠ
------------------------------------------------------------------------------


참고로 JDK 1.6 에서 지원되는 jmap, jhat 사용법을 간단하게 설명 드립니다.

jmap, jhat 은 런타인중인 JVM 의 힙의 내용을 살펴보고자 할 때 사용하고요, 메모리에 있는 대부분의 객체들을 일목 요연하게 볼 수 있습니다.


1. jmap 으로 힙 덤프 파일을 생성한다.

>$ jmap -dump:live,file=heap.bin <pid>

2. jhat 으로 힙 덤프 파일을 분석한다

>$ jhat heap.bin

이렇게 하면 분석이 끝나고 디폴트 7000포트로 웹서버가 하나 뜹니다.

3. 웹 브라우저로 http://<host>:7000/ 로 접속하여 결과를 확인한다.

http://<host>:7000/oql/ 로 접속하면 직접 SQL 과 유사한 OQL (Object Query Language) 를 이용하여 객체들을 뒤져볼 수 있습니다. 

 


Unicode Surrogate Area 캐릭터의 MySQL 입력시 에러 프로그래밍

자바데몬에서 한글을 입력시에 MySQL 에 Insert 하는 과정에서 아래와 같은 Exception 이 종종 발생 했는데.. 며칠 삽질한 끝에 원인을 찾았습니다.

Incorrect string value: '\xF0\x9F\x98\x8D\xF0\x9F...' for column 'NICKNAME' at row 1java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x8D\xF0\x9F...' for column 'NICKNAME' at row 1
    com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1056)
    com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
    com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376)
    com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308)
    com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837)
    com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961)
    com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2543)
    com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2022)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1940)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1925)
    nateon.qsync.table.SyncStatementInfo.sync(SyncStatementInfo.java:100)
    nateon.qsync.table.TableDistribute.syncRecord(TableDistribute.java:118)
    nateon.qsync.QSyncEntry.run(QSyncManager.java:124)
   
문제는 Surrogate Area 라고 불리는 유니코드의 영억 때문인데요.
D800-DBFF, DC00-DFFF 이 영억에는 실제로 매핑된 캐릭터가 오는게 아니고, UTF-16 확장을 위해서 미리 잡아놓은 영역입니다.

JAVA String, Oracle NVARCHAR 는 UTF-16 입니다. (UCS-2 아님)
즉 UCS-4, UCS-8 도 수용이 가능하다는 얘기이지요.


그런데, MySQL의 유니코드 캐릭터셋은 아마도 UTF-16 이 아닌 UCS-2 를 그대로 사용하는 듯 합니다.
그래서 이 예비 영역의 글자가 들어오면 인식하지 못하고 에러를 뱉어 냅니다.

해결책은 아래 샘플 코드 처럼 스트링에서 Surrogate Area 에 있는 캐릭터를 제거하는 겁니다.

 public static String removeSurrogateArea(String val)
 {
  if ( val == null ) return null;
  
  StringBuffer buf = new StringBuffer();
  int len = val.length();
  for ( int i = 0 ; i < len ; i++ )
  {
   char c = val.charAt(i);
   if ( 0xD800 <= c && c <= 0xDBFF ||
     0xDC00 <= c && c <= 0xDFFF )
   {
   }
   else
   {
    buf.append(c);
   }
  }
  return buf.toString();
 }



아래는 wiki 에서 찾은 Surrogates 의 원문입니다.

http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates

SurrogatesThe 2,048 surrogates are not characters, but are reserved for use in UTF-16 to specify code points outside the Basic Multilingual Plane. They are divided into leading or "high surrogates" (D800-DBFF) and trailing or "low surrogates" (DC00-DFFF). In UTF-16, they must always appear in pairs, as a high surrogate followed by a low surrogate, thus using 32 bits to denote one code point.

10000 + (H - D800) × 400 + (L - DC00)
where H and L are the numeric values of the high and low surrogates respectively.

Since high surrogate values in the range DB80-DBFF always produce values in the Private Use planes, the high surrogate range can be further divided into (normal) high surrogates (D800-DB7F) and "high private use surrogates" (DB80-DBFF).


Linux /dev/random vs /dev/urandom 삽질 후기 프로그래밍

프로그래밍을 하다가 보면 가끔 난수를 발생시켜야 하는 때가 있습니다.

난수를 발생시키기 위해서 여러 가지 방법을 사용하는데 몇몇 가지 대표적인 예를 들어 보죠.

1. ANSI C 라이브러리의 rand() 함수를 이용한다.
2. 외부 라이브러리가 (apr 이나, openssl 같은...) 가 제공하는 random 함수들을 이용한다
3. 디바이스 파일을 이용한다 (/dev/random, /dev/urandom)
4. PRNGd 같은 데몬을 이용한다.

여기서 삽질을 겪었던 방법이 3번입니다.

암호화를 위한 아파치 모듈을 하나 만들었는데, 모듈에서 종종 난수를 발생하기 위해서 apr (apache portable runtime library) 에 있는 apr_generate_random_bytes() 라는 함수를 콜하도록 했었고요.

스테이지 테스트까지는 별 문제가 없었는데, 라이브 서버에 올렸더니 일정 시간이 지나면.. apache 가 hang 걸린것 처럼 멈춰 버리는 현상이 발생을 하더군요.

strace 로 system call 을 모니터링 해봤는데,

open("/dev/random", O_RDONLY) = 28
read(28, 0x37ef8824, 4) = ...

여기서 wating 을 하고 있는 것이 아닌가... 몇초도 아니고 몇십초를 대기하고 있었다. 대략 1~2분 정도를..
아파치 소스를 까봤더니 apr_generate_random_byte 내부에서 /dev/random 디바이스를 오픈해서 그냥 읽도록 되어 있었네 그랴.
근데 왜 /dev/random 을 읽다가 wating을 할까....???

man 에서 그 해답을 찾았습니다.

리눅스에서는 /dev/random 과 /dev/urandom 두개를 PRNG 디바이스로 제공을 하는데
방식은 IO 디바이스 드라이버에서 발생하는 입력 신호의 노이즈를 게더링 하여 비트수를 연산하여 난수를 발생시킨다고 하는군요.

/dev/random, /dev/urandom 두개의 차이점은 전자는 엔트로피(얼마나 고른 난수를 발생 시킬 수 있을지를 결정할 수 있는 값이라는군요)가 충분해 져야만 난수를 발생 시키기 때문에 보안에 강하지만 느리고요(심지어지는 엔트로피가 채워잴때까지 blocking 합니다.), 후자는 이 값이 충분하지 않아도 발생을 시키긴 하는데 보안에 취약하지만 빠릅니다.(엔트로피가 채워질때까지 blocking 하지 않습니다. 현재있는 앤트로피만 가지고 생성을 한답니다.)

여기서 보안에 강하다 취약하다는 말은.. 발생된 random 값으로 암호화 키를 생성하는데 사용할 경우, 이 키가 얼마나 깨기 어렵느냐 쉬우냐를 얘기합니다.

서버와 같은 머신에서는 PC 와는 다르게, 키보드나, 마우스와 같은 IO 디바이스가 별로 없기 때문에 일정한 수준의 앤트로피 풀을 만들어 내는데 많은 시간이 걸린다고 합니다.

그리고 /dev/random 은 머신 자체의 영향도 많이 받습니다. 라이브 서버가 5년 전에 들어온 리눅스 머신이었고 스테이지 서버가 들어온지 1년도 안된 새삥 머신이었죠.. /dev/random 에서 wating 하는 걸 두대에서 측정해 봤더니 스테이지 서버가 라이브 서버보다 상당히 빠르더군요..

그래서 결국은 apr 에서 제공하는 random 대신에 /dev/urandom 을 직접 오픈해서 읽는 걸로 바꾸었더니 행 걸리는 현상이 없이 잘 동작 하드랍니다.

참고로 FreeBSD 의 경우에는 /dev/random 은 Linux 의 /dev/urandom 과 같은 방식입니다.
FreeBSD 의 /dev 를 자세히보면 random 이 urandom 으로 symbolic link 가 걸려 있는걸 알 수 가 있습니다.


4년에 한번씩 발생했던 오라클 날짜연산의 문제 프로그래밍

오라클 스토어드 프로시져 중에 날짜 연산을 하기 위한 코드가 있었는데..

30 년전 오늘의 날짜를 구하는 연산이다.

SELECT SYSDATE - TO_YMINTERVAL('30-00') FROM DUAL

얼핏 보면 문제가 없어 보인다.. 그러나 SYSDATE 가 4년에 한번씩 오는 2월 29일 되면 오류가 발생한다.

1978-02-29 이라는 날짜는 존재 하지 않기 때문이다... (쉬트...)

그래서 아래와 같이 수정했다.

SELECT ADD_MONTHS(SYSDATE, -12*30) FROM DUAL


이렇게 연산을 하면 1978-02-28 이라는 결과가 나온다.

못 믿으시겠다고요? 그럼 아래 쿼리를 각각 실행해 보시라..

SELECT TO_DATE('2008-02-29', 'YYYY-MM-DD') + TO_YMINTERVAL('01-00') FROM DUAL

SELECT ADD_MONTHS(TO_DATE('2008-02-29', 'YYYY-MM-DD'), -12*30) FROM DUAL


이 현상은 9.2.0.4 에서 목격되었다.. 상위 버전을 써본적이 없어서 10g 나 11g 에서도 여전히 발생하는지는 모르겠다.

사실 이 문제는 4년전에도 똑같이 겪었다... 소 잃고 외양간도 못 고쳤다... 오늘 또 이런 오류를 겪었다니.. 쩝..


한글 텍스트 파일의 캐릭터셋을 자동으로 판단하는 프로그램 프로그래밍

소스 코드들이 많이 있는 디렉토리가 있다..

내가 작성하는 코드들에 중국어나 일본어가 있으리 만무하지만,

울트라 에디트로 작성한 파일을 많이 저장하다보니...

가끔 서로 다른 캐릭터 셋으로 만들어 놓는 경우가 종종 있다.

윈도에서는 주로

ASCII, EUC-KR, CP-949, UTF-8 등의 캐릭터 셋으로 저장하는데,

가끔 유니코드 텍스트 파일도 BOM 을 넣기도 하고 빼기도 하고 해서,

나도 울트라 에디트로 열어보기 전에는 어떤 캐릭터 셋으로 저장을 해놨는지 헤깔린다.

그래서 그 수많은 파일 중에서 UTF-8 으로 저장된 파을을 찾으려고 했더니 시간이 너무 많이 걸리더라.. ㅋㅋ

인터넷을 뒤져서 캐릭터셋을 자동으로 판별해 주는 코드를 입수해 보려고 노력을 했으나..

문득... 인터넷 뒤지는 시간이나 내가 만드는 시간이나 별반 차이가 없으리라 생각하고..

이틀간 위키를 뒤져가며 각 캐릭터셋의 인코딩 방식을 찾아서 뚝딱뚝딱 코딩을 했는데.. 생각보다 잘 동작 한다. ㅋㅋ

US-ASCII, EUC-KR, MS-949, UTF-8 형태로 된 인코딩을 자동으로 찾아서 판별해 준다..

물론 BOM 이 붙은 넘들은 BOM 만 가지고 판별을 하고 BOM 이 없는 넘들은 인코딩 오토마타의 간략버전으로 판별한다.

US-ASCII ⊂ EUC-KR ⊂ MS-949
US-ASCII ⊂ UTF-8

의 포함 관계는 대충 아시리라 보고..

유닉스 같은 데서는

find . -type f | xargs detectcharset

이런 식으로 하시면 대략 파일들의 캐릭터 셋을 아시리라

detectcharset.zip

트랙백이 뭘까?

Oracle 에서 DES, Triple DES 의 적용

트랙백을 보내면 원문과 링크가 연결된다고 한다..

첨 시도해 봄...

Oracle 에서 DES, Triple DES 의 적용 암호화

어제 영선 과장님이 오라클에서 Triple DES 로 암호화를 했는데, 자바로 암호화 한거랑 다르다는 거다

왜 다른지 물어보러 왔었는데, 알고보니 IV (Initial Vector) 값 때문이었다. DES, Triple DES, AES 류들은 모두 블럭 암호화 방식인데 이런 블럭 암호화 방식들은 암호화를 하기 위해서 암호화 키 말도고 필요한 몇가지가 있는데, 그런 것들을 파라미터라하고..

다음과 같은 파라미터를
  • Block Mode
  • Padding Mode
  • Initial Vector
  • Shift Bits
가지고 있다.

Block Mode

실 파라미터라기 보다는 알고리즘과 함쳐져서 하나의 알고리즘 이름 처럼 불리는 경우가 많다.
즉 예를 들어서
  • DES-ECB, DES-CBC, DES-CFB, DES-OFB
  • DES3-ECB, DES3-CBC, DES3-CFB, DES3-OFB
  • AES128-ECB, AES128-CBC, AES128-CFB, AES128-OFB
위와 같이 말이다... 블락 모드는 아래와 같다.
  • ECB : Eletric Code Book
  • CBC : Cipher Block Chain
  • OFB : Output Feed Back
  • CFB : Cipher Feed Back
Padding Mode

이건 블락 사이즈에 맞춰서 내용을 패딩할지 말지를 말하는데, PKCS#5 표준 패딩을 사용한다.

Initial Vector

ECB 모드를 제외한 나머지 알고리즘은 다음 블럭을 암호화 할 때 이전 블럭의 결과값 같은걸 XOR 하여 다시 한번 연산을 하는 방식인데, 이 경우 첫번째 블럭에서 이전 블럭의 결과값이 없기 때문에 임으로 암호화하는 쪽에서 값을 지정해야 한다, 암호화에서 사실 이 값은 노출이 되어도 별로 상관이 없다고 얘기를 한다.

Shift Bits

CFB 모드에서는 이전 블락을 연산할때 그냥 하는게 아니라 비트를 돌려서 하는 알고리즘을 가지고 있다. 그래서 그때 비트르 돌릴때 몇 비트식 돌릴지를 결정하는 파라미터 이다.

Oracle 에서도 암호화를 지원하는데 현재 DES, Triple DES 정도를 지원하고 있다. 그리고 도큐먼트에 특별히 다른 모드를 지정하는게 없는걸로 보아 CBC 모드 인것 같다.

아.. 더 작성할려 했는데 귀찮아 지려고 한다... 다음 번에 계속 해서...

1


끝말잇기