티스토리 뷰

데이터 무결성이란

일반적으로 '데이터 무결성'이라고 함은 큰 범주에서 데이터베이스에서 데이터의 정확성과 일관성을 보증하는 것을 의미한다. 이런 데이터의 무결성을 보증할 수 없는 경우 우리는 '데이터가 변질되었다' 라고 할 수 있다. 이는 데이터가 우리가 기대하던 원본과 달라졌음을 의미한다.

일반적으로 파일이나 네트워크에서 무결성을 검증하기 위해 체크섬(checksum) 을 이용하고, 프로그래밍 언어에서는 해시값(hashvalue) 를 이용한다. 이 둘은 데이터의 무결성을 보장하기 위해 단 하나의 비트(bit) 의 데이터라도 수정이 되면 전체 해시값에 영향을 주어 원본과 일치하지 않는 해시값이 된다. 이 원본 해시값을 사본 해시값과 비교하면 데이터의 무결성이 보장되는지 쉽게 알 수 있다.

데이터와 관련된 소프트웨어 개발에서 무결성을 지키기란 쉽지 않다. 특히 여러 운영체제에서 동작하는 소프트웨어라면 운영체제의 특성과 관련된 부분으로 데이터 무결성이 쉽게 깨지곤 한다.

해시값을 통한 데이터 무결성

일반적으로 프로그래밍 언어에서의 데이터는 숫자형과 문자형이 있는데, 대부분 문자형의 데이터에서 데이터의 무결성이 깨지기 쉽다. 여러 운영체제에서 사용하는 개행 문자 값이 다르기 때문이다. 일반적 개발 툴에서는 개행 문자의 비트값은 우리 눈에 보이지 않는다.

  • 윈도우: CRLF (\r\n - &#A)
  • 맥: CR (\r - &#D)
  • 유닉스, 리눅스: LF (\n - &#A)

CR(Carriage Return) 은 0x0D 값이고,
LF(Line Feed) 는 0x0A 값이다.

이는 아주 간단한 실험으로 테스트해 볼 수 있다. 아래와 같이 일반적으로 "엔터키"를 누르면 추가되는 개행 문자 값은 모두 다른 것을 알 수 있다.

https://repl.it/@powerumc/string-carriage-return

일부 해시값을 계산하는 방법으로 공격하는 보안적인 취약점이 발견되어 일부 프로그래밍 언어의 특정 버전, 특정 플랫폼에서는 매번 해시값이 변한다.
예로 .NET Framework, .NET Core 와 Python 3.3 이상 버전부터는 새로운 프로세스가 실행되면 해시값도 항상 변하게 된다.

읽어볼거
Why is string.GetHashCode() different each time I run my program in .NET Core?https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/

using System;

class MainClass {
  public static void Main (string[] args) {
    var cr = "\r";
    var lf = "\n";
    var crlf = "\r\n";

    Console.WriteLine($"CR Hashcode={cr.GetHashCode()}");
    Console.WriteLine($"LF Hashcode={lf.GetHashCode()}");
    Console.WriteLine($"CRLF Hashcode={crlf.GetHashCode()}");

    Console.WriteLine($"cr = lf is {cr == lf}");
    Console.WriteLine($"cr = crlf is {cr == crlf}");
    Console.WriteLine($"lf = crlf is {cr == crlf}");
  }
}

// Results
// CR Hashcode=1948159545
// LF Hashcode=-1646523816
// CRLF Hashcode=-1196730459
// cr = lf is False
// cr = crlf is False
// lf = crlf is False

데이터 무결성이 깨지는 API

간단하게 테스트를 해볼 수 있는 다음의 XML 데이터를 다루는 소스코드를 준비했다. 원본 데이터의 개행 문자 값은 \r\n 이지만, 어떤 API 를 사용하느냐에 따라 반환되는 개행 문자 값은 달라진다. 만약 이런 API 들을 혼용해서 사용한다면 당연히 데이터의 무결성을 깨지게 된다.

  1. XmlTextReader 는 개행 문자 값 그대로 반환
  2. XmlReader\n 값으로 반환
  3. XmlDocument 는 개행 문자 값 그대로 반환
  4. XDocument \n 값으로 반환

https://repl.it/@powerumc/xml-carriage-return

데이터의 무결성이 깨지는 윈도우 클라이언트 프로그래밍

일반적으로 윈도우 클라이언트 프로그래밍을 할 여러 행의 문자열을 입력 받을 수 있는 컨트롤이 여기에 해당 된다. 이런 컨트롤은 내부적으로 Environment.NewLine 을 이용하는데, Environment.NewLine 자체가 운영체제에 해당하는 개행 문자 값을 반환한다.

예를 들어, 윈도우에서 구동되는 WPFTextBox 컨트롤이 개행 문자 값은 항상 \r\n 이 된다.

<TextBox AcceptsReturn="True"></TextBox>

데이터의 무결성이 깨지는 웹 프로그래밍

웹에서는 또 어떨까? 일반적으로 개행 문자를 입력 받을 수 있는 TextArea 의 개행 문자 값은 \n 이다. 이는 아래의 테스트 코드에서 확인해 볼 수 있다.

show 버튼을 클릭하면 자바스크립트로 개행 문자를 텍스트로 표시해 주도록 했고, submit 버튼을 클릭하면 서버로 폼의 데이터가 전송되도록 했다. 여기에서 눈여겨 보아야 할 것이 있는데 클라이언트의 결과와 서버로 전송된 데이터는 개행 문자가 달라진다.

  • HTML TextArea 컨트롤은 개행 문자를 \n 을 사용한다
  • Form 전송 시 기본 값인 enctype="application/x-www-form-urlencoded" 인 경우 개행문자는 \n 로 치환된다

Form 전송 시 Request Header 정보는 아래와 같다.

:method: POST
:path: /submit
:scheme: https
content-type: application/x-www-form-urlencoded

아래는 Form 전송 시 URL Encoded 된 Form Data 값이다. %0D%0A 값에서 알 수 있듯이 \n 개행 문자가 \r\n 로 치환된 것을 알 수 있다.

txt: Hello%0D%0AWorld

https://repl.it/@powerumc/html-textarea-carriage-return-by-node

클라이언트 HTML 코드 (index.html)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>html textarea carriage return test</title>
  </head>
  <body>
    <form action="/submit" method="POST">
      <textarea id="txt" name="txt" style="height: 200px; width: 200px;"></textarea>
      <button id="btn" type="button">show</button>
      <span id="span"></span>
      <button type="submit">submit</button>
    </form>
  </body>

  <script>
    document.querySelector("#btn").addEventListener("click",
      function() {
        var text = document.querySelector("#txt").value
          .replace(/\r/g, "\\r")
          .replace(/\n/g, "\\n");

        document.querySelector("#span").innerText = text;
      });
  </script>
  </body>
</html>

서버 자바스크립트 코드 (index.js)

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile('public/index.html');
});

app.post("/submit", (req, res) => {
  var txt = req.body.txt;
  res.send(txt.replace(/\r/g, "\\r")
          .replace(/\n/g, "\\n"));
});

app.listen(3000, () => console.log('server started'));

읽어볼거리
HTML textarea의 개행문자는 무엇일까? (LF vs CRLF vs 상황에 따라 다르다 vs 충격과 공포)
https://libsora.so/posts/what-is-textarea-newline/

마무리

일반적인 서비스/비즈니스 개발에서 개행 문자로 인해 해시값이 달라지는 문제는 크게 의미가 없을 수 있다. 그러나 다양한 운영체제를 지원하는 크로스 플랫폼에서는 문제가 될 수 있다. 사용자에게 보이는 화면의 텍스트의 한 줄의 빈 공백이 두 줄이 되는 경우가 있고, 데이터를 파일과 같은 저장소에 저장하는 경우 개행 문자가 달라지는 경우도 발생한다. 모바일 게임에서 이 개행 문자 하나로 해시 값이 달라져 데이터 파일을 1GB 를 다운로드 받는다고 생각하면 정말 끔찍한 일이다.

윈도우 클라이언트에서, 모바일 기기에서, 웹 페이지에서, 다양한 운영체제의 클라이언트에서 입력한 같은 데이터를 프로그래밍 언어는 다르다고 해석할 수 있다. 이것이 비즈니스에 영향을 줄 수 있다면 올바로 바로잡는 것도 좋을 것이다.

댓글