Ubuntu 20.04LTS + mariadb 10.5 (replication)

문자메시지 발송서비스 서버 구축을 위해서 mariadb (replication) 를 사용
1,000,000건/일 + @ 처리가 가능해야 한다

서버스펙

Xeon (16core)
samsung 860 pro 1Tb * 2 (RAID0)
32Gb memory

구성

L4 : Active – Standby
mariadb : Master – Slave

서버설치

http://mirror.kakao.com/ubuntu-releases/focal/

mariadb 설치

sudo apt-get install software-properties-common dirmngr apt-transport-https
sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] https://mirror.yongbok.net/mariadb/repo/10.5/ubuntu focal main'

sudo apt update
sudo apt install mariadb-server

mysql 사용자 설정

mysql -uroot -p
# create user username@localhost identified by 'password';
create user my@'%' identified by '1234';
# grant select on database.table to username@localhost;
grant select on *.* to my@'%';
# grant all privileges on database.table to username@localhost;
grant all privileges on *.* to my@'%';
flush privileges;

replication

master

[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log

vim /etc/mysql/mariadb.conf.d/50-server.cnf

systemctl restart mariadb

Welcome to the MariaDB monitor.  Commands end with ; or \g.
 Your MariaDB connection id is 37
 Server version: 10.5.9-MariaDB-1:10.5.9+maria~focal-log mariadb.org binary distribution
 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
 Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 MariaDB [(none)]> show master status;
 +------------------+----------+--------------+------------------+
 | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
 +------------------+----------+--------------+------------------+
 | mysql-bin.000001 |     2098 |              |                  |
 +------------------+----------+--------------+------------------+
 1 row in set (0.000 sec)
 MariaDB [(none)]>
show master status;

slave

[mysqld]
server-id = 2
relay_log=mysql-relay-bin
log_slave_updates = 1
read_only = 1

vim /etc/mysql/mariadb.conf.d/50-server.cnf

systemctl restart mariadb

Welcome to the MariaDB monitor.  Commands end with ; or \g.
 Your MariaDB connection id is 36
 Server version: 10.5.9-MariaDB-1:10.5.9+maria~focal mariadb.org binary distribution
 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
 Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

 MariaDB [(none)]> stop slave;
 MariaDB [(none)]> CHANGE MASTER TO
     -> MASTER_HOST='###.###.###.###',
     -> MASTER_PORT=3306,
     -> MASTER_USER='repl',
     -> MASTER_PASSWORD='1234',
     -> MASTER_LOG_FILE='mysql-bin.000001',
     -> MASTER_LOG_POS=2098;
 MariaDB [(none)]> start slave;
 MariaDB [(none)]> show slave status \G;
 * 1. row *
                 Slave_IO_State: Waiting for master to send event
                    Master_Host: ###.###.###.###
                    Master_User: repl
                    Master_Port: 3306
                  Connect_Retry: 60
                Master_Log_File: mysql-bin.000001
            Read_Master_Log_Pos: 2098
                 Relay_Log_File: mysql-relay-bin.000002
                  Relay_Log_Pos: 906
          Relay_Master_Log_File: mysql-bin.000001
               Slave_IO_Running: Yes
              Slave_SQL_Running: Yes
                Replicate_Do_DB:
            Replicate_Ignore_DB:
             Replicate_Do_Table:
         Replicate_Ignore_Table:
        Replicate_Wild_Do_Table:
    Replicate_Wild_Ignore_Table:
                     Last_Errno: 0
                     Last_Error:
                   Skip_Counter: 0
            Exec_Master_Log_Pos: 2098
                Relay_Log_Space: 1215
                Until_Condition: None
                 Until_Log_File:
                  Until_Log_Pos: 0
             Master_SSL_Allowed: No
             Master_SSL_CA_File:
             Master_SSL_CA_Path:
                Master_SSL_Cert:
              Master_SSL_Cipher:
                 Master_SSL_Key:
          Seconds_Behind_Master: 0
  Master_SSL_Verify_Server_Cert: No
                  Last_IO_Errno: 0
                  Last_IO_Error:
                 Last_SQL_Errno: 0
                 Last_SQL_Error:
    Replicate_Ignore_Server_Ids:
               Master_Server_Id: 1
                 Master_SSL_Crl:
             Master_SSL_Crlpath:
                     Using_Gtid: No
                    Gtid_IO_Pos:
        Replicate_Do_Domain_Ids:
    Replicate_Ignore_Domain_Ids:
                  Parallel_Mode: optimistic
                      SQL_Delay: 0
            SQL_Remaining_Delay: NULL
        Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
               Slave_DDL_Groups: 3
 Slave_Non_Transactional_Groups: 0
     Slave_Transactional_Groups: 0
 1 row in set (0.000 sec)
 ERROR: No query specified
 MariaDB [(none)]>
show slave status \G;

PhpSpreadsheet – Tips

$spreadsheet = new Spreadsheet();
$spreadsheet->getProperties()->setCreator('jongwan')
     ->setTitle('')
     ->setSubject('')
     ->setDescription('');

PhpSpreadsheet 초기화하기

$styleArray = [
     'borders' => [
         'allBorders' => [
             'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN
         ],
         'outline' => [
             'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK
         ]
     ]
 ];
 $spreadsheet->getActiveSheet()->getStyle('A1:D10')->applyFromArray($styleArray);

외곽선과 내부선을 스타일 지정하는 방법

// 숫자형 셀스타일
$spreadsheet->getActiveSheet()->setCellValueExplicit('A1', '내용', \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);

// 문자형 셀스타일
$spreadsheet->getActiveSheet()->setCellValueExplicit('B1', '내용', \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);

// 지정없이 입력
$spreadsheet->getActiveSheet()->setCellValue('C1', '내용');

셀에 데이터타입을 지정하기

header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="filename.xls"');
header('Cache-Control: no-cache');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT+9'); // always modified
header('Pragma: public'); // HTTP/1.0
$writer = IOFactory::createWriter($spreadsheet, 'Xls');
$writer->save('php://output');

파일로 다운로드하기

// 첫번째 시트 선택 (0번부터 시작)
$spreadsheet->setActiveSheetIndex(0);
$spreadsheet->getActiveSheet()->setTitle('sheet name');

// 시트 생성하기
$spreadsheet->createSheet();
$spreadsheet->setActiveSheetIndex(1);
$spreadsheet->getActiveSheet()->setTitle("상세내역");

시트 생성, 선택, 이름지정하기

$spreadsheet->getActiveSheet()->mergeCells('A1:D2');

셀합치기(merge)

$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP);
$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM);


$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT);

셀내용 정렬하기

$spreadsheet->getActiveSheet()->getStyle('A1:D5')->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setRGB('dddddd');

배경색 지정하기

phpspreadsheet 설치하기

# apt install composer
# composer require phpoffice/phpspreadsheet

컴포져phpspreadsheet 설치하기

Do not run Composer as root/super user! See https://getcomposer.org/root for details
 Warning from https://packagist.org: You are using an outdated version of Composer. Composer 2.0 is now available and you should upgrade. See https://getcomposer.org/2
 Using version ^1.16 for phpoffice/phpspreadsheet
 ./composer.json has been created
 Loading composer repositories with package information
 Warning from https://packagist.org: You are using an outdated version of Composer. Composer 2.0 is now available and you should upgrade. See https://getcomposer.org/2
 Updating dependencies (including require-dev)
 Your requirements could not be resolved to an installable set of packages.
 Problem 4
     - Installation request for phpoffice/phpspreadsheet ^1.16 -> satisfiable by phpoffice/phpspreadsheet[1.16.0].
     - phpoffice/phpspreadsheet 1.16.0 requires ext-dom * -> the requested PHP extension dom is missing from your system.
     - phpoffice/phpspreadsheet 1.16.0 requires ext-gd * -> the requested PHP extension gd is missing from your system. 
 To enable extensions, verify that they are enabled in your .ini files:
     - phpoffice/phpspreadsheet 1.16.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system. 
     - phpoffice/phpspreadsheet 1.16.0 requires ext-zip * -> the requested PHP extension zip is missing from your system. 
     - /etc/php/7.2/cli/php.ini
     - /etc/php/7.2/cli/conf.d/10-mysqlnd.ini
     - /etc/php/7.2/cli/conf.d/10-opcache.ini
     - /etc/php/7.2/cli/conf.d/10-pdo.ini
     - /etc/php/7.2/cli/conf.d/20-calendar.ini
     - /etc/php/7.2/cli/conf.d/20-ctype.ini
     - /etc/php/7.2/cli/conf.d/20-exif.ini
     - /etc/php/7.2/cli/conf.d/20-fileinfo.ini
     - /etc/php/7.2/cli/conf.d/20-ftp.ini
     - /etc/php/7.2/cli/conf.d/20-gettext.ini
     - /etc/php/7.2/cli/conf.d/20-iconv.ini
     - /etc/php/7.2/cli/conf.d/20-json.ini
     - /etc/php/7.2/cli/conf.d/20-mysqli.ini
     - /etc/php/7.2/cli/conf.d/20-pdo_mysql.ini
     - /etc/php/7.2/cli/conf.d/20-phar.ini
     - /etc/php/7.2/cli/conf.d/20-posix.ini
     - /etc/php/7.2/cli/conf.d/20-readline.ini
     - /etc/php/7.2/cli/conf.d/20-shmop.ini
     - /etc/php/7.2/cli/conf.d/20-sockets.ini
     - /etc/php/7.2/cli/conf.d/20-sysvmsg.ini
     - /etc/php/7.2/cli/conf.d/20-sysvsem.ini
     - /etc/php/7.2/cli/conf.d/20-sysvshm.ini
     - /etc/php/7.2/cli/conf.d/20-tokenizer.ini
   You can also run php --ini inside terminal to see which files are used by PHP in CLI mode.
 Installation failed, deleting ./composer.json.
# apt install -y php-xml php-gd php-mbstring php-zip

오류메시지를 확인하고 설치되지 않은 패키지를 설치한다.

Do not run Composer as root/super user! See https://getcomposer.org/root for details
 Warning from https://packagist.org: You are using an outdated version of Composer. Composer 2.0 is now available and you should upgrade. See https://getcomposer.org/2
 Using version ^1.16 for phpoffice/phpspreadsheet
 ./composer.json has been created
 Loading composer repositories with package information
 Warning from https://packagist.org: You are using an outdated version of Composer. Composer 2.0 is now available and you should upgrade. See https://getcomposer.org/2
 Updating dependencies (including require-dev)
 Package operations: 11 installs, 0 updates, 0 removals
 Installing ezyang/htmlpurifier (v4.13.0): Downloading (100%)
 Installing psr/http-message (1.0.1): Downloading (100%)
 Installing psr/http-factory (1.0.1): Downloading (100%)
 Installing psr/http-client (1.0.1): Downloading (100%)
 Installing psr/simple-cache (1.0.1): Downloading (100%)
 Installing markbaker/matrix (2.1.1): Downloading (100%)
 Installing markbaker/complex (2.0.0): Downloading (100%)
 Installing myclabs/php-enum (1.7.7): Downloading (100%)
 Installing symfony/polyfill-mbstring (v1.22.0): Downloading (100%)
 Installing maennchen/zipstream-php (2.1.0): Downloading (100%)
 Installing phpoffice/phpspreadsheet (1.16.0): Downloading (100%)
 phpoffice/phpspreadsheet suggests installing mpdf/mpdf (Option for rendering PDF with PDF Writer)
 phpoffice/phpspreadsheet suggests installing dompdf/dompdf (Option for rendering PDF with PDF Writer (doesn't yet support PHP8))
 phpoffice/phpspreadsheet suggests installing tecnickcom/tcpdf (Option for rendering PDF with PDF Writer (doesn't yet support PHP8))
 phpoffice/phpspreadsheet suggests installing jpgraph/jpgraph (Option for rendering charts, or including charts with PDF or HTML Writers)
 Writing lock file
 Generating autoload files 

설치완료

google reCAPTCHA

어드민 콘솔 접속 주소

https://www.google.com/recaptcha/admin

HTML 폼작성하기

<!-- recaptcha element -->
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
<input type="hidden" id="recaptcha_response" name="recaptcha_response" value="" />
<div id="recaptcha_area"></div>

<script type="text/javascript">
var verify_value = "";
var onloadCallback = function() {
	grecaptcha.render('recaptcha_area', {
		'sitekey' : '{어드민 콘솔에서 발급받은 사이트키}',
		'callback' : function(response) {
			if (response != "") {
				verify_value = response;
			}
		},
		'theme' : 'light'
	});
};
</script>
<!-- //recaptcha element -->

<script>
// 폼서브밋시
$("#recaptcha_response").val(verify_value);
</script>

FORM DATA 처리 (php)

<?php
// recaptcha 확인
include_once 'Snoopy.class.php';
$snoopy = new Snoopy;
$data = array(
	"secret" => '어드민 콘솔에서 발급받은 시크릿키',
	"response" => $recaptcha_response
);
$snoopy->submit('https://www.google.com/recaptcha/api/siteverify', $data);
$response = json_decode($snoopy->results);
if (!$response->success) {
	// fail
	exit;
} else {
	// success
}

Snoopy.class.php

https://github.com/endroy/Snoopy

도로명주소 API 서비스

도로명 주소 API 서비스

행정자치부에서 제공하는 OPEN API 서비스

 

API 신청페이지

https://www.juso.go.kr/addrlink/devAddrLinkRequestWrite.do?returnFn=write&cntcMenu=URL

 

PHP + Snoopy로 주소찾기 예제

코드

[code lang=”php”]

<?php
require_once ‘Snoopy.class.php’;

$params = array(
‘currentPage’ => 1,
‘countPerPage’ => 5,
‘resultType’ => ‘json’,
‘confmKey’ => ‘{발급받은 키문자열}’,
‘keyword’ => ‘서울시 마포구 합정동 10’
);

$snoopy = new Snoopy();
$snoopy->httpmethod = "POST";
$snoopy->submit("http://www.juso.go.kr/addrlink/addrLinkApiJsonp.do", $params);
$response = $snoopy->results;

// 앞뒤에 ‘(‘, ‘)’ 문자를 제거한 뒤 json_decode()를 이용해 변환
$json = substr($response, 1, -1);
$obj = json_decode($json);

[/code]

 

출력결과

[code]

stdClass Object
(
[results] => stdClass Object
(
[common] => stdClass Object
(
[errorMessage] => 정상
[countPerPage] => 5
[totalCount] => 73
[errorCode] => 0
[currentPage] => 1
)

[juso] => Array
(
[0] => stdClass Object
(
[detBdNmList] =>
[engAddr] => 56, Tojeong-ro, Mapo-gu, Seoul
[rn] => 토정로
[emdNm] => 합정동
[zipNo] => 04082
[roadAddrPart2] => (합정동)
[sggNm] => 마포구
[jibunAddr] => 서울특별시 마포구 합정동 55-1
[siNm] => 서울특별시
[roadAddrPart1] => 서울특별시 마포구 토정로 56
[bdNm] =>
[admCd] => 1144012200
[udrtYn] => 0
[lnbrMnnm] => 55
[roadAddr] => 서울특별시 마포구 토정로 56 (합정동)
[lnbrSlno] => 1
[buldMnnm] => 56
[bdKdcd] => 0
[liNm] =>
[rnMgtSn] => 114403113023
[mtYn] => 0
[bdMgtSn] => 1144011800100010000009632
[buldSlno] => 0
)

[1] => stdClass Object
(
[detBdNmList] =>
[engAddr] => 35, Tojeong-ro 4-gil, Mapo-gu, Seoul
[rn] => 토정로4길
[emdNm] => 합정동
[zipNo] => 04085
[roadAddrPart2] => (합정동, 신성빌라)
[sggNm] => 마포구
[jibunAddr] => 서울특별시 마포구 합정동 75-10 신성빌라
[siNm] => 서울특별시
[roadAddrPart1] => 서울특별시 마포구 토정로4길 35
[bdNm] => 신성빌라
[admCd] => 1144012200
[udrtYn] => 0
[lnbrMnnm] => 75
[roadAddr] => 서울특별시 마포구 토정로4길 35 (합정동, 신성빌라)
[lnbrSlno] => 10
[buldMnnm] => 35
[bdKdcd] => 1
[liNm] =>
[rnMgtSn] => 114404139606
[mtYn] => 0
[bdMgtSn] => 1144012200100750010024923
[buldSlno] => 0
)

[2] => stdClass Object
(
[detBdNmList] =>
[engAddr] => 13-5, Tojeong-ro 4an-gil, Mapo-gu, Seoul
[rn] => 토정로4안길
[emdNm] => 합정동
[zipNo] => 04085
[roadAddrPart2] => (합정동)
[sggNm] => 마포구
[jibunAddr] => 서울특별시 마포구 합정동 82-10
[siNm] => 서울특별시
[roadAddrPart1] => 서울특별시 마포구 토정로4안길 13-5
[bdNm] =>
[admCd] => 1144012200
[udrtYn] => 0
[lnbrMnnm] => 82
[roadAddr] => 서울특별시 마포구 토정로4안길 13-5 (합정동)
[lnbrSlno] => 10
[buldMnnm] => 13
[bdKdcd] => 0
[liNm] =>
[rnMgtSn] => 114404139607
[mtYn] => 0
[bdMgtSn] => 1144012200100820010012210
[buldSlno] => 5
)

[3] => stdClass Object
(
[detBdNmList] =>
[engAddr] => 19, Tojeong-ro 4an-gil, Mapo-gu, Seoul
[rn] => 토정로4안길
[emdNm] => 합정동
[zipNo] => 04085
[roadAddrPart2] => (합정동)
[sggNm] => 마포구
[jibunAddr] => 서울특별시 마포구 합정동 82-10
[siNm] => 서울특별시
[roadAddrPart1] => 서울특별시 마포구 토정로4안길 19
[bdNm] =>
[admCd] => 1144012200
[udrtYn] => 0
[lnbrMnnm] => 82
[roadAddr] => 서울특별시 마포구 토정로4안길 19 (합정동)
[lnbrSlno] => 10
[buldMnnm] => 19
[bdKdcd] => 0
[liNm] =>
[rnMgtSn] => 114404139607
[mtYn] => 0
[bdMgtSn] => 1144012200100820010024925
[buldSlno] => 0
)

[4] => stdClass Object
(
[detBdNmList] =>
[engAddr] => 13-4, Tojeong-ro 4an-gil, Mapo-gu, Seoul
[rn] => 토정로4안길
[emdNm] => 합정동
[zipNo] => 04085
[roadAddrPart2] => (합정동)
[sggNm] => 마포구
[jibunAddr] => 서울특별시 마포구 합정동 82-10
[siNm] => 서울특별시
[roadAddrPart1] => 서울특별시 마포구 토정로4안길 13-4
[bdNm] =>
[admCd] => 1144012200
[udrtYn] => 0
[lnbrMnnm] => 82
[roadAddr] => 서울특별시 마포구 토정로4안길 13-4 (합정동)
[lnbrSlno] => 10
[buldMnnm] => 13
[bdKdcd] => 0
[liNm] =>
[rnMgtSn] => 114404139607
[mtYn] => 0
[bdMgtSn] => 1144012200100820010012213
[buldSlno] => 4
)

)

)

)

[/code]

ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION

untitled-1

ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION

어느날 갑자기 다운로드가 되지 않고 이런 오류메시지가 나와서 알아보니 크롬 최신버전에서 헤더가 변경되었다한다.

아래처럼 변경 (PHP)

Header(“Content-Disposition: attachment; filename=$filename”);

Header(“Content-Disposition: attachment; filename=\”$filename\”“);

파일명을 쌍따옴표로 묶어준다.

PHP – HTML DOM 파서

Simple Html DOM Parser

http://sourceforge.net/projects/simplehtmldom/

C#의 Html Agility Pack(https://htmlagilitypack.codeplex.com/)같이 PHP에서 사용이 가능한 DOM 파서가 필요해 구글링을 해보니 Simplehtmldom 이라는 좋은 라이브러리가 있었다.

네이버 연합뉴스의 제목만 가져오기

[code lang=”php”]
$html = file_get_html(‘http://news.naver.com/main/list.nhn?mode=LPOD&mid=sec&oid=422&listType=title’);
foreach($html->find(‘.list_body .type02 a’) as $e) {
echo $e->innertext;
}
[/code]

다음 연령별 뉴스의 모든 노드를 출력

[code lang=”php”]
$html = file_get_html(‘http://media.daum.net/ranking/age/’);
$dump = dump_html_tree($html);
echo "<xmp style=’text-align: left;’>";
print_r($dump);
echo "</xmp>";
[/code]

Google reCAPTCHA PHP (CURL/Snoopy) Sample

logo_recaptcha_color_24dp Google reCAPTCHA

입력폼

[code lang=”html”]
<form onsubmit="return check_form()">
<input type="hidden" id="recaptcha_response" name="recaptcha_response" value="" />
<div id="recaptcha1"></div>
<button type="submit">확인</button>
</form>
[/code]

[code lang=”javascript”]
<script type="text/javascript">
var recaptchaWidger1;
var onloadCallback = function() {
recaptchaWidger1 = grecaptcha.render(‘recaptcha1’, {
‘sitekey’ : ‘{Site key}’
});
};
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>

<script>
function check_form() {
var recaptch_response = grecaptcha.getResponse(recaptchaWidger1);
if (!recaptch_response) {
alert("자동가입방지 문자를 확인해 주세요");
return false;
}

document.getElementById("recaptcha_response").value = recaptch_response;

return true;
}
</script>
[/code]

폼데이터 처리(1) – Snoopy.lib.php

[code lang=”php”]
include_once ‘Snoopy.class.php’;
$snoopy = new Snoopy;
$data = array(
"secret" => "Secret key",
"response" => $_POST[‘recaptcha_response’]
);
$snoopy->submit(‘https://www.google.com/recaptcha/api/siteverify’, $data);
$response = json_decode($snoopy->results);
if (!$response->success) {
exit("fail message");
}
[/code]

폼데이터 처리(2) – CURL

[code lang=”php”]
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "secret={Secret key}&response=".$_POST[‘recaptcha_response’]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);
curl_close ($ch);
$response = json_decode($output);
if (!$response->success) {
exit("fail message");
}
[/code]

C#, PHP – RIJNDAEL 256bit 암/복호화

C#과 php간에 데이터를 주고 받아야 하는데
평문으로 통신하기에 무리가 있던 도중
기존에 사용하던 php 암호화 라이브러리와 연동되는 c# 코드를 발견~
살짝 수정해서 사용하는데 무리가 없음

기본은 128bit지만 256bit로 변경했음
php의 경우 블록에서 남는 부분을 zero로 채워넣기 때문에
c#에서 PaddingMode.Zeros를 설정해야 정상적으로 동작합니다.

Encryption.cs

[csharp]
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace TestClass
{
class Encryption
{
private static byte[] rijnKey = Encoding.UTF8.GetBytes("abcdefg_abcdefg_abcdefg_abcdefg_");
private static byte[] rijnIV = Encoding.UTF8.GetBytes("abcdefg_abcdefg_abcdefg_abcdefg_");

public static String EncryptIt(String s)
{
String result;
RijndaelManaged rijn = new RijndaelManaged();
rijn.Mode = CipherMode.CBC;
rijn.Padding = PaddingMode.Zeros; // php와의 연동에서 꼭 확인
rijn.BlockSize = 256;

using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = rijn.CreateEncryptor(rijnKey, rijnIV))
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(s);
}
}
}
result = Convert.ToBase64String(msEncrypt.ToArray());
}
rijn.Clear();

result = Base64UrlEncode(result);

return result;
}

public static String DecryptIt(String s)
{
String result;
RijndaelManaged rijn = new RijndaelManaged();
rijn.Mode = CipherMode.CBC;
rijn.Padding = PaddingMode.Zeros;
rijn.BlockSize = 256;

s = Base64UrlDecode(s);

using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(s)))
{
using (ICryptoTransform decryptor = rijn.CreateDecryptor(rijnKey, rijnIV))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader swDecrypt = new StreamReader(csDecrypt))
{
result = swDecrypt.ReadToEnd();
}
}
}
}
rijn.Clear();

return result;
}

public static string Base64UrlEncode(string arg)
{
arg = arg.Split(‘=’)[0]; // Remove any trailing ‘=’s
arg = arg.Replace(‘+’, ‘-‘); // 62nd char of encoding
arg = arg.Replace(‘/’, ‘_’); // 63rd char of encoding
return arg;
}

public static string Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace(‘-‘, ‘+’); // 62nd char of encoding
s = s.Replace(‘_’, ‘/’); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing ‘=’s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception("Illegal base64url string!");
}
return s;
}
}
}

[/csharp]

test.php

[php]
//Encryption function
function encryption($encrypt, $key, $iv)
{
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, ”, MCRYPT_MODE_CBC, ”);
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $encrypt);
$encode = base64_encode($encrypted);
$encode = base64safeEncode($encode);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

return $encode;
}

//Decryption function
function decryption($decrypt, $key, $iv)
{
$decoded = base64safeDecode($decrypt);
$decoded = base64_decode($decoded);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, ”, MCRYPT_MODE_CBC, ”);
mcrypt_generic_init($td, $key, $iv);
$decrypted = mdecrypt_generic($td, $decoded);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

return trim($decrypted);
}

function base64safeEncode($data) {
$data = str_replace("=", "", $data);
$data = str_replace("+", "-", $data);
$data = str_replace("/", "_", $data);
return $data;
}

function base64safeDecode($data) {
$data = str_replace("-", "+", $data);
$data = str_replace("_", "/", $data);

switch (strlen($data) % 4) {
case 0: break;
case 2: $data .= "=="; break;
case 3: $data .= "="; break;
}

return $data;
}

$key = "abcdefg_abcdefg_abcdefg_abcdefg_";
$iv = "abcdefg_abcdefg_abcdefg_abcdefg_";

echo encryption("Hello world!", $key, $iv);
echo decryption("1ERpu8RaiyMqsXdjhCEu9IVMyQlz/A3fBR6vGbNxaVs=", $key, $iv);
[/php]

screw 로 php 소스코드 암호화

php_screw 설치

php_screw 설치하기를 참고하여 php_screw.so를 익스텐션에 올린다

 

screw 컴파일

php_screw 소스를 압축해제한 디렉토리에 tools가 있다
cd tools
make

screw 컴파일

실행파일을 접근가능한 디렉토리로 복사해준다. 여기서는 /usr/bin/ 으로 복사했다
cp screw /usr/bin

 

php 파일 암호화

테스트할 파일을 만든다
echo "" > phpinfo.php

암호화한다
screw phpinfo.php

php_screw

위에처럼 phpinfo.php, phpinfo.php.screw 파일이 생성된다
phpinfo.php 는 암호화 되었고
phpinfo.php.screw 는 원본파일이다