Python – GPG

ทดสอบบน Ubuntu 20.04.5 LTS ใน WSL 2

  1. Generate a key
  2. Export keys
  3. Import keys
  4. List keys
  5. Encrypt a string
  6. Decrypt a string
  7. Encrypt a file
  8. Decrypt a file

Installation

ติดตั้ง gnupg (น่าจะติดตั้งอยู่แล้ว)

sudo apt-get install gnupg 
$ gpg --version
gpg (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/jack/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

ติดตั้ง python-gnupg

pip install python-gnupg

1. Generate a key

import os
import gnupg

os.system('rm -rf /home/jack/gpghome')
os.system('mkdir  /home/jack/gpghome')

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
input_data = gpg.gen_key_input(
    key_length=4096,
    name_email='mr.phaisarn@gmail.com',
    passphrase='my passphrase')
key = gpg.gen_key(input_data)
print(key)
84511C0301E2D51E59D79C91B5432E86F4B8925A

2. Export keys

export ค่า public key และ private key ไว้ในไฟล์ mykeyfile.asc

import gnupg

key='84511C0301E2D51E59D79C91B5432E86F4B8925A'

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
ascii_armored_public_keys = gpg.export_keys(key,passphrase='my passphrase')
ascii_armored_private_keys = gpg.export_keys(key, True, passphrase='my passphrase')
with open('mykeyfile.asc', 'w') as f:
    f.write(ascii_armored_public_keys)
    f.write(ascii_armored_private_keys)
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGNqDu4BEADY6n5U0Fs4YBhAejiPZu5hI5i3oYbscK5xQSSXvgkUwoQkmHKQ
JVv+Chq4+jCguBSlmVXXMNnk45F7OhjuxpfrXDSxQPivwchXTh0117UEtTi5jE2s
eZTP5fMSdHomdD4NgU9PfSUiSPNH/PGnSQ8AFstFdFff846ry+dPu3XJdbkkVtEP
Cbv6idGCa2u/ZMukj2o2ZbvOXNVj1M5RFOpqJ6m52UQXA1W8S/Qr3g7gJ41jTWE3
oOP1oSfw5IbuO6yTrhOjwELTM2BexXwUAEtW59A8e+1JVh1xtbiTRxmnC+WHZ1T1
RQ9a7rFbFyqmboZ5RE8wUX7SsklzPyXtZhCnsFfBt3OVJNyD5hPoZ4qq7ihGeIIa
OD2g2VDu+uHeJg4Vqd8YcafFruLs0WR0BAdliYHClhhGSTQ2mFK7jQDfAVZYuFhZ
kKqd7WV7++e/oyLViWxDvhBN1XQ7Wbw5o1hkgeH6cyFg9nX/psVecXCriOJJUyVh
AKeFypLgfUiXkNoNuiWIcyJer55wIzjV0oTcLmHLtedh4Bh0+qvI+EI2UnKW2Ih0
Xas9jE3nx97T5365MD7hDjjm7sqmbg3xbih2VXUC+WjPuaisqbGgM/3vO06VA2A4
jg3j1SUm0N4IN8LSFzmAtvoSc5kvHDzTgySCYwlC3YowjKPciMu24jfjXwARAQAB
tClBdXRvZ2VuZXJhdGVkIEtleSA8bXIucGhhaXNhcm5AZ21haWwuY29tPokCTgQT
AQoAOBYhBIRRHAMB4tUeWdeckbVDLob0uJJaBQJjag7uAhsvBQsJCAcCBhUKCQgL
AgQWAgMBAh4BAheAAAoJELVDLob0uJJaL1AP+QEa1Uc1etOnSlz8V6IV5vcpPn0i
6T1cAAb0yfHELKScL4zC6oPq2AyWTTJUT9T/qiHtWi38bM90fkNfefpMdMHeh0TQ
GsTBeAO5YdF+I5GERib2m1vKJjaPVOQWaW0GhGo9fhI8TBdK0b4138JvHpdVsqub
/KbsoRXriXQxrYEy7bnl7Rsr2rYmRlFN5MGkxbVjmHYg1FJle6e6kCRD3sNs2juD
a1HDqYk6/MF6D5LlQ/o7tI53zeRrSAoOvY+yEnmp9a6Pb2X8vw3IGEzP12uad84X
sD2BhXLntie47svDqSbzOdPD0ON/W6M5DuCtWM9qwMrYHI8QyZqLG2sY7Mmz/2kQ
9R9+nU2O5I5sYWpkcW4olMEpe7jaiUNlq8tErIoOxDonc9tUrumoHOQvuxVodnSr
f1zJ5ypbE+v2Cz8MHw5hoBwQWokxZ8qrTo1zP3XvDe9tM5LykFOo8O1RJ1YmCJ58
qaHpC5jIZ/b+2/YKClG3XUxqFVBHLl5YvdfEXnJzOqRHLmakd7HyOgUbMBV0FtYf
E+ipdjfRPXsLh5cbF+HoVkScrcY+qmTSdN1m4c1ffLO419SAvhKgdC51VAd8UdoQ
ZYjx3vD06ghHO8sNyvuUL9z0XmuJykNxC1eVVnxgV8gf16OVymwMxcu5RPMIDtGS
0H7kHgfl3AmPCLrW
=m6l6
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PRIVATE KEY BLOCK-----

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
CY8IutY=
=6b0l
-----END PGP PRIVATE KEY BLOCK-----

3. Import keys

import gnupg
from pprint import pprint

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
key_data = open('mykeyfile.asc').read()
import_result = gpg.import_keys(key_data)
pprint(import_result.results)
[{'fingerprint': '84511C0301E2D51E59D79C91B5432E86F4B8925A',
  'ok': '0',
  'text': 'Not actually changed\n'},
 {'fingerprint': '84511C0301E2D51E59D79C91B5432E86F4B8925A',
  'ok': '0',
  'text': 'Not actually changed\n'},
 {'fingerprint': '84511C0301E2D51E59D79C91B5432E86F4B8925A',
  'ok': '16',
  'text': 'Not actually changed\nContains private key\n'}]

4. List keys

import gnupg
from pprint import pprint

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
public_keys = gpg.list_keys()
private_keys = gpg.list_keys(True)
print('public keys:')
pprint(public_keys)
print('private keys:')
pprint(private_keys)
public keys:
[{'algo': '1',
  'cap': 'escaESCA',
  'compliance': '23',
  'curve': '',
  'date': '1667895022',
  'dummy': '',
  'expires': '',
  'fingerprint': '84511C0301E2D51E59D79C91B5432E86F4B8925A',
  'flag': '',
  'hash': '',
  'issuer': '',
  'keygrip': 'AADC0ABF3CA1285034094D0381E0142B513D5514',
  'keyid': 'B5432E86F4B8925A',
  'length': '4096',
  'origin': '0',
  'ownertrust': 'u',
  'sig': '',
  'sigs': [],
  'subkeys': [],
  'token': '',
  'trust': 'u',
  'type': 'pub',
  'uids': ['Autogenerated Key <mr.phaisarn@gmail.com>'],
  'updated': ''}]
private keys:
[{'algo': '1',
  'cap': 'escaESCA',
  'compliance': '23',
  'curve': '',
  'date': '1667895022',
  'dummy': '',
  'expires': '',
  'fingerprint': '84511C0301E2D51E59D79C91B5432E86F4B8925A',
  'flag': '',
  'hash': '',
  'issuer': '',
  'keygrip': 'AADC0ABF3CA1285034094D0381E0142B513D5514',
  'keyid': 'B5432E86F4B8925A',
  'length': '4096',
  'origin': '0',
  'ownertrust': 'u',
  'sig': '',
  'sigs': [],
  'subkeys': [],
  'token': '+',
  'trust': 'u',
  'type': 'sec',
  'uids': ['Autogenerated Key <mr.phaisarn@gmail.com>'],
  'updated': ''}]

5. Encrypt a string

import gnupg

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
unencrypted_string = 'Who are you? How did you get in my house?'
encrypted_data = gpg.encrypt(unencrypted_string, 'mr.phaisarn@gmail.com')
encrypted_string = str(encrypted_data)

print('ok: ', encrypted_data.ok)
print('status: ', encrypted_data.status)
print('stderr: ', encrypted_data.stderr)
print('unencrypted_string: ', unencrypted_string)
print('encrypted_string: ', encrypted_string)
ok:  True
status:  encryption ok
stderr:  gpg: WARNING: unsafe permissions on homedir '/home/jack/gpghome'
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 2
[GNUPG:] ENCRYPTION_COMPLIANCE_MODE 23
[GNUPG:] BEGIN_ENCRYPTION 2 9
[GNUPG:] END_ENCRYPTION

unencrypted_string:  Who are you? How did you get in my house?
encrypted_string:  -----BEGIN PGP MESSAGE-----

hQIMA7VDLob0uJJaAQ//czHG42eeN1rtFTo6qdvoHSa/B3uJNeFSobUi4gUCxceh
yVbZ0La8pnLMy2JmA2agYaOvC6OhAXfNOhPXV5jinMTvwGB+dKC1qaAYXMknQjyF
5whvmQeBbuQX8AoKxs7hAYmlQO8mrHDBoP2xSdY/AgLeqdlnbZNcDR+NIpoPJS8a
zr+KHYAhEyZT0Bm6FAw7DvhriB4nJRLeMLoIQKIJTN3ZeEdlcZAt5xYL24dZfz8/
4Qpn++ItqsCQfXJ1leJIIE/oB4E83/J4FZuS1xh7n9OomBT2fRG8hmGlSzFYepX/
jDzjApmA/R4HGd6BuJZdNy6SV2DERs2NlTRko98IDFdCIg2Jp7jsSGSZowsKD7z5
rIU110iwR6hRropsuX18jlRk6QM1Um+dSGbHXTUk1OzUDE5uC3hBZsB0s9M2q5DS
HN+EbyatT9hB9X5Obf7w9Vj+Jta9q5voL/riZ2d+GFr8oR0d594An7Dup12zBff/
kdV7DgibAqorCD1MvVePOpvYw2NdhukRluOsVtXySkcAGy8VssBLoW3kqilOvrhq
pCJUG157TnhKnYW0l4BJ7KVoNFwBGsJKzS0Drh5Iq6LmiqHsKv7neK9LkApDeehD
EhdNoGN79fPvEYmync2bbGbVq6F7czHADwVKqNv0aDxaCrFHASsSXkGNbqMQ+tnS
YgFfzEOmYoDR7UjEog/OrBJAnPSgDcwn3I0xuU3RtDaEvlHCt6aX+NHEyIUvsIe+
Don8uUXi4VkjO2uoCOyNgwndoigDuouh8NDv5DIWcKt+KMGPkgKEmfbOxJNA0Rl5
TK5E
=Jyon
-----END PGP MESSAGE-----

6. Decrypt a string

import gnupg

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
unencrypted_string = 'Who are you? How did you get in my house?'
encrypted_data = gpg.encrypt(unencrypted_string, 'mr.phaisarn@gmail.com')
encrypted_string = str(encrypted_data)
decrypted_data = gpg.decrypt(encrypted_string, passphrase='my passphrase')

print('ok: ', decrypted_data.ok)
print('status: ', decrypted_data.status)
print('stderr: ', decrypted_data.stderr)
print('decrypted string: ', decrypted_data.data)
ok:  True
status:  decryption ok
stderr:  gpg: WARNING: unsafe permissions on homedir '/home/jack/gpghome'
[GNUPG:] ENC_TO B5432E86F4B8925A 1 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] DECRYPTION_KEY 84511C0301E2D51E59D79C91B5432E86F4B8925A 84511C0301E2D51E59D79C91B5432E86F4B8925A u
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
gpg: encrypted with 4096-bit RSA key, ID B5432E86F4B8925A, created 2022-11-08
      "Autogenerated Key <mr.phaisarn@gmail.com>"
[GNUPG:] BEGIN_DECRYPTION
[GNUPG:] DECRYPTION_COMPLIANCE_MODE 23
[GNUPG:] DECRYPTION_INFO 2 9
[GNUPG:] PLAINTEXT 62 1667895570
[GNUPG:] PLAINTEXT_LENGTH 41
[GNUPG:] DECRYPTION_OKAY
[GNUPG:] GOODMDC
[GNUPG:] END_DECRYPTION

decrypted string:  b'Who are you? How did you get in my house?'

7. Encrypt a file

import gnupg

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
open('my-unencrypted.txt', 'w').write('You need to Google Venn diagram.')
with open('my-unencrypted.txt', 'rb') as f:
    status = gpg.encrypt_file(
        f, recipients=['mr.phaisarn@gmail.com'],
        output='my-encrypted.txt.gpg')

print('ok: ', status.ok)
print('status: ', status.status)
print('stderr: ', status.stderr)
ok:  True
status:  encryption ok
stderr:  gpg: WARNING: unsafe permissions on homedir '/home/jack/gpghome'
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 2
[GNUPG:] ENCRYPTION_COMPLIANCE_MODE 23
[GNUPG:] BEGIN_ENCRYPTION 2 9
[GNUPG:] END_ENCRYPTION
-----BEGIN PGP MESSAGE-----

hQIMA7VDLob0uJJaARAAhwNuaLm5/eSCV81Uw+H2E536AzzXm/yPxM0CIjLjYxnE
+GwgvBxJsYtuuwCElUfYkIKYNzAWOGGRje961Be3+g5kjFfphvl4I1Xzwb5RKdWa
i9raKNQ1/PLQ6Hr8Fc3yh08QcgLkihyRgVKZ3suTX+KRzWd3/SS8rjS27nigQk8C
IoIuwJIsBYpB8xbBDM+Sfo2XJm938Z7n9ZfdDiuO0hEWBo5qF85Vh0jY91BVyPqD
yGYcsivK/OqRG/VrPTrp01+2+jBn5HPZoPCnjEZdyJ3p+k/bnvAymKpKmPfEHrt3
4FwJ0EIfFROx6u2xBWhoRAhl5vLjIgRqccwEHD9+HrT3aFyeQhOKcQoV0TXy5Y0w
rYLRBny2oolCGW1NqZi43pjamQXXVpJLDjOsn8pzhdbFiSxUrFZj2WXET7ItQaVd
fQQnPxpOpj7LOOncLdZ0qfGMYjNNXGoKlaPfdtJy1yQlz8mUUdZZAjAdCpKpnfca
YxuFmzMy2gGBKsYYrLB2FH6HasRbjb5uQ1qO72vybuPWCBNkX5jgWHGLddNagxR7
9EwMnvu958Oy3TiFfrr7YKWPIOQXaLx8WsgXCCl2rFJKG4Hy81ZM5k7DDQzzQFGd
w+a5FWo/cthXm6qx2LSE2ByFyAerqwe/T/wkAYmrQLIJdxZIWxp5xsuPBS6W6oLS
WwEiSHXqJPjqKHKnZwS0W9kWq/68voNf03QlEkN0+Hm3PRmEFIJnpw19BtfQEoY3
Rv/XCQXE2G6NVMajvFj72fvFzTUUyVRa8m//nu2wg9vG5JMjJbThA8L8+5E=
=WZm8
-----END PGP MESSAGE-----

ถ้า import public key แล้วนำมาใช้ encrypt จะได้ error invalid recipient

ok:  False
status:  invalid recipient
stderr:  gpg: WARNING: unsafe permissions on homedir `/home/jack/gpghome'
gpg: EAECD251: There is no assurance this key belongs to the named user
[GNUPG:] INV_RECP 10 84511C0301E2D51E59D79C91B5432E86F4B8925A
gpg: [stdin]: encryption failed: Unusable public key

ให้เพิ่ม gpg.trust_keys()

import gnupg

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
gpg.trust_keys('84511C0301E2D51E59D79C91B5432E86F4B8925A', 'TRUST_ULTIMATE')
open('my-unencrypted.txt', 'w').write('You need to Google Venn diagram.')
with open('my-unencrypted.txt', 'rb') as f:
    status = gpg.encrypt_file(
        f, recipients='84511C0301E2D51E59D79C91B5432E86F4B8925A',
        output='encrypted.txt.gpg')

print('ok: ', status.ok)
print('status: ', status.status)
print('stderr: ', status.stderr)

8. Decrypt a file

import gnupg

gpg = gnupg.GPG(gnupghome='/home/jack/gpghome')
with open('my-encrypted.txt.gpg', 'rb') as f:
    status = gpg.decrypt_file(f, passphrase='my passphrase', output='my-decrypted.txt')

print('ok: ', status.ok)
print('status: ', status.status)
print('stderr: ', status.stderr)
ok:  True
status:  decryption ok
stderr:  gpg: WARNING: unsafe permissions on homedir '/home/jack/gpghome'
[GNUPG:] ENC_TO B5432E86F4B8925A 1 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
[GNUPG:] DECRYPTION_KEY 84511C0301E2D51E59D79C91B5432E86F4B8925A 84511C0301E2D51E59D79C91B5432E86F4B8925A u
[GNUPG:] KEY_CONSIDERED 84511C0301E2D51E59D79C91B5432E86F4B8925A 0
gpg: encrypted with 4096-bit RSA key, ID B5432E86F4B8925A, created 2022-11-08
      "Autogenerated Key <mr.phaisarn@gmail.com>"
[GNUPG:] BEGIN_DECRYPTION
[GNUPG:] DECRYPTION_COMPLIANCE_MODE 23
[GNUPG:] DECRYPTION_INFO 2 9
[GNUPG:] PLAINTEXT 62 1667895671
[GNUPG:] PLAINTEXT_LENGTH 32
[GNUPG:] DECRYPTION_OKAY
[GNUPG:] GOODMDC
[GNUPG:] END_DECRYPTION

Joblib

Install

pip install joblib

การหา square root ด้วย Python

from math import sqrt
li = [sqrt(i ** 2) for i in range(10)]
print(li)
# [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

ใช้ joblib

from math import sqrt
from joblib import Parallel, delayed
li = Parallel(n_jobs=2)(delayed(sqrt)(i**2) for i in range(10))
print(li)
# [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

verbose: int, optional

The verbosity level: if non zero, progress messages are printed. Above 50, the output is sent to stdout. The frequency of the messages increases with the verbosity level. If it more than 10, all iterations are reported.

%python
from math import sqrt
from joblib import Parallel, delayed
li = Parallel(n_jobs=2, verbose=100)(delayed(sqrt)(i**2) for i in range(10))
print(li)

# [Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
# [Parallel(n_jobs=2)]: Done   1 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   2 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   3 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   4 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   5 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   6 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   7 tasks      | elapsed:    0.5s
# [Parallel(n_jobs=2)]: Done   8 out of  10 | elapsed:    0.5s remaining:    0.1s
# [Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    0.5s remaining:    0.0s
# [Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    0.5s finished
# [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

default backend เป็น loky เราสามารถเปลี่ยนเป็น threading , multiprocessing ได้

from math import sqrt
from joblib import Parallel, delayed
li = Parallel(n_jobs=2, verbose=100, backend='threading')(delayed(sqrt)(i**2) for i in range(10))
print(li)

MkDocs

MkDocs is a fast, simple and downright gorgeous static site generator that’s geared towards building project documentation. Documentation source files are written in Markdown, and configured with a single YAML

Installation

To install MkDocs, run the following command from the command line:

pip install mkdocs
> mkdocs -h
Usage: mkdocs [OPTIONS] COMMAND [ARGS]...

  MkDocs - Project documentation with Markdown.

Options:
  -V, --version  Show the version and exit.
  -q, --quiet    Silence warnings
  -v, --verbose  Enable verbose output
  -h, --help     Show this message and exit.

Commands:
  build      Build the MkDocs documentation
  gh-deploy  Deploy your documentation to GitHub Pages
  new        Create a new MkDocs project
  serve      Run the builtin development server
> mkdocs --version
mkdocs, version 1.3.1 from c:\users\jack\python38-venv\env\lib\site-packages\mkdocs (Python 3.8)

For more details, see the Installation Guide.

Creating a new project

Getting started is super easy. To create a new project, run the following command from the command line:

mkdocs new my-project
cd my-project
$ tree
.
├── docs
│   └── index.md
└── mkdocs.yml

There’s a single configuration file named mkdocs.yml, and a folder named docs that will contain your documentation source files (docs is the default value for the docs_dir configuration setting). Right now the docs folder just contains a single documentation page, named index.md.

ไฟล์ mkdocs.yml

site_name: My Docs

ไฟล์ docs/index.md

# Welcome to MkDocs

For full documentation visit [mkdocs.org](https://www.mkdocs.org).

## Commands

* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs -h` - Print help message and exit.

## Project layout

    mkdocs.yml    # The configuration file.
    docs/
        index.md  # The documentation homepage.
        ...       # Other markdown pages, images and other files.

MkDocs comes with a built-in dev-server that lets you preview your documentation as you work on it. Make sure you’re in the same directory as the mkdocs.yml configuration file, and then start the server by running the mkdocs serve command:

> mkdocs serve
INFO     -  Building documentation...
INFO     -  Cleaning site directory
INFO     -  Documentation built in 0.24 seconds
INFO     -  [21:14:45] Watching paths for changes: 'docs', 'mkdocs.yml'
INFO     -  [21:14:45] Serving on http://127.0.0.1:8000/

Open up http://127.0.0.1:8000/ in your browser, and you’ll see the default home page being displayed:

Fenced code blocks

The fenced code blocks extension adds an alternate method of defining code blocks without indentation.

The first line should contain 3 or more backtick (`) characters, and the last line should contain the same number of backtick characters (`):

```
Fenced code blocks are like Standard
Markdown’s regular code blocks, except that
they’re not indented and instead rely on
start and end fence lines to delimit the
code block.
```

With this approach, the language can optionally be specified on the first line after the backticks which informs any syntax highlighters of the language used:

```python
def fn():
    pass
```

```sql
SELECT * FROM FOO
```

```c
main() {
    print('Hello');
}
```

Note that fenced code blocks can not be indented. Therefore, they cannot be nested inside list items, blockquotes, etc.

Building the site

That’s looking good. You’re ready to deploy the first pass of your MkLorum documentation. First build the documentation:

mkdocs build

This will create a new directory, named site. Take a look inside the directory:

$ ls site
about  fonts  index.html  license  search.html
css    img    js          mkdocs   sitemap.xml

Notice that your source documentation has been output as two HTML files named index.html and about/index.html. You also have various other media that’s been copied into the site directory as part of the documentation theme. You even have a sitemap.xml file and mkdocs/search_index.json.

If you’re using source code control such as git you probably don’t want to check your documentation builds into the repository. Add a line containing site/ to your .gitignore file.

echo "site/" >> .gitignore

If you’re using another source code control tool you’ll want to check its documentation on how to ignore specific directories.

Python installed path

sys.executable

A string giving the absolute path of the executable binary for the Python interpreter, on systems where this makes sense. If Python is unable to retrieve the real path to its executable, sys.executable will be an empty string or None.

>>> import sys
>>> sys.executable
'C:\\Python310\\python.exe'

os.path.dirname(path)

Return the directory name of pathname path. This is the first element of the pair returned by passing path to the function split().

>>> import os
>>> import sys
>>> os.path.dirname(sys.executable)
'C:\\Python310'
>>> import os
>>> import sys
>>> os.path.dirname(sys.executable)
'C:\\Users\\<username>\\AppData\\Local\\Programs\\Python\\Python38\\python.exe'

Python – List all files and directories in a directory

List all files and directories in a directory

from os import listdir
my_path = '/home/jack'
files_list = listdir(my_path)
print(files_list)

List all files in a directory

from os import listdir
from os.path import isfile, join
my_path = '/home/jack'
files_list = [f for f in listdir(my_path) if isfile(join(my_path, f))]
print(files_list)

List all directories in a directory

from genericpath import isdir
from os import listdir
from os.path import isfile, join
my_path = '/home/jack'
files_list = [f for f in listdir(my_path) if isdir(join(my_path, f))]
print(files_list)

Python DateTime

datetime.date

from datetime import date
today = date.today()

print(type(today))
# <class 'datetime.date'>
print(today)
# 2022-08-19

datetime.datetime

เวลาปัจจุบันของ server

from datetime import datetime
now = datetime.now() # current date and time

print(type(now))
# <class 'datetime.datetime'>
print(now)
# 2022-08-19 07:51:55.893002

นำเวลาปัจจุบันของ server มาบวก 7 ชั่วโมง

import datetime
now = datetime.datetime.now()
print(now)
# 2022-08-19 08:32:36.803853

now = now + datetime.timedelta(hours=7)
print(now)
# 2022-08-19 15:32:36.803853
from datetime import datetime
now = datetime.now() # current date and time

year = now.strftime("%Y")
print("year:", year)
# year: 2022

month = now.strftime("%m")
print("month:", month)
# month: 08

day = now.strftime("%d")
print("day:", day)
# day: 19

time = now.strftime("%H:%M:%S")
print("time:", time)
# time: 08:12:42

date_time = now.strftime("%m/%d/%Y, %H:%M:%S")
print("date and time:",date_time)	
# date and time: 08/19/2022, 08:12:42

time.struct_time

from datetime import datetime
now = datetime.now() # current date and time
time_tuple = now.timetuple()

print(type(time_tuple))
# <class 'time.struct_time'>

print(now)
# 2022-08-19 07:55:03.144893

print(time_tuple)
# time.struct_time(tm_year=2022, tm_mon=8, tm_mday=19, tm_hour=7, tm_min=55, tm_sec=3, tm_wday=4, tm_yday=231, tm_isdst=-1)

timestamp

หาค่า timestamp จาก current date และ time

import time
from datetime import datetime
now = datetime.now() # current date and time
time_tuple = now.timetuple()
timestamp = int(time.mktime(time_tuple))

print(now)
# 2022-08-19 07:59:21.232243

print(timestamp)
# 1660895961

เปลี่ยน type ของข้อมูล (float, int, str, bytes)

import time
from datetime import datetime
now = datetime.now() # current date and time
time_tuple = now.timetuple()
timestamp = time.mktime(time_tuple)

print(type(timestamp))
print(timestamp)
# <class 'float'>
# 1660898887.0

timestamp = int(timestamp)
print(type(timestamp))
print(timestamp)
# <class 'int'>
# 1660898887

timestamp = str(timestamp)
print(type(timestamp))
print(timestamp)
# <class 'str'>
# 1660898887

timestamp = str.encode(timestamp)
print(type(timestamp))
print(timestamp)
# <class 'bytes'>
# b'1660898887'

timestamp = timestamp.decode()
print(type(timestamp))
print(timestamp)
# <class 'str'>
# 1660898887

หาค่า timestamp จากวันที่ ที่กำหนด

import time
from datetime import datetime

# date in string format
dt="19/08/2022"

time_tuple = datetime.strptime(dt, "%d/%m/%Y").timetuple()
print(time_tuple)
# time.struct_time(tm_year=2022, tm_mon=8, tm_mday=19, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=231, tm_isdst=-1)

timestamp = time.mktime(time_tuple)
print(timestamp)
# 1660867200.0

Python cryptography 

Installation

pip install cryptography

Introduction

cryptography includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and key derivation functions. For example, to encrypt something with cryptography’s high level symmetric encryption recipe:

from cryptography.fernet import Fernet
# Put this somewhere safe!
key = Fernet.generate_key()
print(key.hex())
f = Fernet(key)
token = f.encrypt(b"A really secret message. Not for prying eyes.")
print(token)
# b'...'
print(f.decrypt(token))
# b'A really secret message. Not for prying eyes.'
print(token.hex())

Layout

cryptography is broadly divided into two levels. One with safe cryptographic recipes that require little to no configuration choices. These are safe and easy to use and don’t require developers to make many decisions.

The other level is low-level cryptographic primitives. These are often dangerous and can be used incorrectly. They require making decisions and having an in-depth knowledge of the cryptographic concepts at work. Because of the potential danger in working at this level, this is referred to as the “hazardous materials” or “hazmat” layer. These live in the cryptography.hazmat package, and their documentation will always contain an admonition at the top.

We recommend using the recipes layer whenever possible, and falling back to the hazmat layer only when necessary.

Example

สร้าง key แล้วเก็บไว้ในไฟล์ secret.key

from cryptography.fernet import Fernet

# Use Fernet to generate the key file.
key = Fernet.generate_key()

# Store the file to disk to be accessed for en/de:crypting later.
with open('secret.key', 'wb') as new_key_file:
    new_key_file.write(key)
print(key)
# print(key.decode('utf-8'))
print(key.hex())

นำ key จากในไฟล์ secret.key มา encrypt ข้อความ

from cryptography.fernet import Fernet
# Load the private key from a file.
with open('secret.key', 'rb') as my_private_key:
    key = my_private_key.read()

msg = "Into the valley of death, rode the 600."
# Encode this as bytes to feed into the algorithm.
# (Refer to Encoding types above).
msg = msg.encode()
# print(msg)

# Instantiate the object with your key.
f = Fernet(key)
# Pass your bytes type message into encrypt.
ciphertext = f.encrypt(msg)
print(ciphertext)

นำ key จากในไฟล์ secret.key มา decrypt ข้อความ

from cryptography.fernet import Fernet
# Load the private key from a file.
with open('secret.key', 'rb') as my_private_key:
    key = my_private_key.read()

# Instantiate Fernet on the recip system.
f = Fernet(key)

ciphertext = b'gAAAAABi9LSeFJbUUaMH3Ra-xDJkj_U_1xbs2dpzhoHQT2mTCSLIYQYBMvqYIJtrkzu1jI-IyTQCiLGCvKZfcB09Fq_wfUuAVZneIn3mg3Nz3QV4vUvT3N5xk0Iu1VfpGmdWIkrUET8_'

# Decrypt the message.
cleartext = f.decrypt(ciphertext)
# Decode the bytes back into a string.
cleartext = cleartext.decode()
print(cleartext)

Using passwords with Fernet

import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
password = b"password"
salt = os.urandom(16)  # IV
print(salt.hex())
kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,
    salt=salt,
    iterations=390000,
)
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
token = f.encrypt(b"Secret message!")
print(token)
# b'...'
print(f.decrypt(token))
# b'Secret message!'

In this scheme, the salt has to be stored in a retrievable location in order to derive the same key from the password in the future.

The iteration count used should be adjusted to be as high as your server can tolerate. A good default is at least 480,000 iterations, which is what Django recommends as of July 2022.

Implementation

Fernet is built on top of a number of standard cryptographic primitives. Specifically it uses:

  • AES in CBC mode with a 128-bit key for encryption; using PKCS7 padding.
  • HMAC using SHA256 for authentication.
  • Initialization vectors are generated using os.urandom().

The CBC mode requires an IV, and this IV is generated by os.urandom(). (Python 3 Fernet encrypting same message different ways – Stack Overflow)

Python ECDSA and ECDH

This is an easy-to-use implementation of ECC (Elliptic Curve Cryptography) with support for ECDSA (Elliptic Curve Digital Signature Algorithm), EdDSA (Edwards-curve Digital Signature Algorithm) and ECDH (Elliptic Curve Diffie-Hellman), implemented purely in Python, released under the MIT license. With this library, you can quickly create key pairs (signing key and verifying key), sign messages, and verify the signatures. You can also agree on a shared secret key based on exchanged public keys. The keys and signatures are very short, making them easy to handle and incorporate into other protocols.

NOTE: This library should not be used in production settings, see Security for more details.

Installation

pip install ecdsa

Usage

You start by creating a SigningKey. You can use this to sign data, by passing in data as a byte string and getting back the signature (also a byte string). You can also ask a SigningKey to give you the corresponding VerifyingKey. The VerifyingKey can be used to verify a signature, by passing it both the data string and the signature byte string: it either returns True or raises BadSignatureError.

from ecdsa import SigningKey
sk = SigningKey.generate() # uses NIST192p
vk = sk.verifying_key
signature = sk.sign(b"message")
assert vk.verify(signature, b"message")
from ecdsa import SigningKey
sk = SigningKey.generate()  # uses NIST192p
vk = sk.verifying_key
bytes_text = bytes("The first sample string|The second sample string", 'utf-8')
signature = sk.sign(bytes_text)
assert vk.verify(signature, bytes_text)

Each SigningKey/VerifyingKey is associated with a specific curve, like NIST192p (the default one). Longer curves are more secure, but take longer to use, and result in longer keys and signatures.

from ecdsa import SigningKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
signature = sk.sign(b"message")
assert vk.verify(signature, b"message")

The SigningKey can be serialized into several different formats: the shortest is to call s=sk.to_string(), and then re-create it with SigningKey.from_string(s, curve) . This short form does not record the curve, so you must be sure to pass to from_string() the same curve you used for the original key. The short form of a NIST192p-based signing key is just 24 bytes long. If a point encoding is invalid or it does not lie on the specified curve, from_string() will raise MalformedPointError.

from ecdsa import SigningKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
sk_string = sk.to_string()
sk2 = SigningKey.from_string(sk_string, curve=NIST384p)
print(sk_string.hex())
print(sk2.to_string().hex())
from ecdsa import SigningKey, NIST256p
sk = SigningKey.generate(curve=NIST256p)
vk = sk.verifying_key
signature = sk.sign(b"message")
assert vk.verify(signature, b"message")
print("OK")

sk_string = sk.to_string()
sk_hex = sk_string.hex()
print(sk_hex)

sig_hex = signature.hex()
print(sig_hex)

Note: while the methods are called to_string() the type they return is actually bytes, the “string” part is leftover from Python 2.

sk.to_pem() and sk.to_der() will serialize the signing key into the same formats that OpenSSL uses. The PEM file looks like the familiar ASCII-armored "-----BEGIN EC PRIVATE KEY-----" base64-encoded format, and the DER format is a shorter binary form of the same data. SigningKey.from_pem()/.from_der() will undo this serialization. These formats include the curve name, so you do not need to pass in a curve identifier to the deserializer. In case the file is malformed from_der() and from_pem() will raise UnexpectedDER or MalformedPointError.

from ecdsa import SigningKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
sk_pem = sk.to_pem()
sk2 = SigningKey.from_pem(sk_pem)
# sk and sk2 are the same key

Likewise, the VerifyingKey can be serialized in the same way: vk.to_string()/VerifyingKey.from_string()to_pem()/from_pem(), and to_der()/from_der(). The same curve= argument is needed for VerifyingKey.from_string().

from ecdsa import SigningKey, VerifyingKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
vk_string = vk.to_string()
vk2 = VerifyingKey.from_string(vk_string, curve=NIST384p)
# vk and vk2 are the same key

from ecdsa import SigningKey, VerifyingKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
vk_pem = vk.to_pem()
vk2 = VerifyingKey.from_pem(vk_pem)
# vk and vk2 are the same key

There are a couple of different ways to compute a signature. Fundamentally, ECDSA takes a number that represents the data being signed, and returns a pair of numbers that represent the signature. The hashfunc= argument to sk.sign() and vk.verify() is used to turn an arbitrary string into a fixed-length digest, which is then turned into a number that ECDSA can sign, and both sign and verify must use the same approach. The default value is hashlib.sha1, but if you use NIST256p or a longer curve, you can use hashlib.sha256 instead.

Python List

What is a List in Python?

A list is a data type in Python where you can store multiple values of different data types (including nested lists).

numList = [1, 2, 3, 4, 5]

stringList = ["banana", "orange", "apple"]

mixedList = [1, "banana", "orange", [5, 6]]

You can access items in a list using their index position. Index positions start from 0 in lists:

stringList = ["banana", "orange", "apple"]

print(stringList[1])
# "orange"

another example

li = ['a', 0, 1.234]
# ['a', 0, 1.234]

li.append(True)
# ['a', 0, 1.234, True]

li[0]
# 'a'

li[-1]
# True

li[1:3]
# [0, 1.234]

li[2:]
# [1.234, True]

li[:2]
# ['a', 0]

li[-3:-1]
# [0, 1.234]
li = [1,2,3]
li.append(4)
print(li)
li.pop()
print(li)
li.pop(0)
print(li)
# [1, 2, 3, 4]
# [1, 2, 3]
# [2, 3]
li = [1,2,3]
print(3 in li)
print(4 in li)
# True
# False
nested = [1,2,['a','b']]
# [1, 2, ['a', 'b']]

nested[2]
# ['a', 'b']

nested[2][1]
# 'b'

How to Sort Lists in Python

You can sort a list in Python using the sort() method.

The sort() method allows you to order items in a list. Here’s the syntax:

list.sort(reverse=True|False, key=sortFunction)

By default, you can order strings and numbers in ascending order, without passing an argument to this method:

items = ["orange", "cashew", "banana"]

items.sort()
# ['banana', 'cashew', 'orange']

For descending order, you can pass the reverse argument:

items = [6, 8, 10, 5, 7, 2]

items.sort(reverse=True)
# [10, 8, 7, 6, 5, 2]

How to specify a sort function

items = [
  {
    'name': 'John',
    'age': 40
  },
  {   
    'name': 'Mike',
    'age': 45
  },
  {   
    'name': 'Jane',
    'age': 33
  },
  {   
    'name': 'Asa',
    'age': 42
  }
]

def sortFn(dict):
  return dict['age']

items.sort(key=sortFn)
# [
#   {'name': 'Jane', 'age': 33},
#   {'name': 'John', 'age': 40},
#   {'name': 'Asa', 'age': 42},
#   {'name': 'Mike', 'age': 45}
# ]

If the reverse argument is passed as True here, the sorted dictionaries will be in descending order.

items = ["cow", "elephant", "duck"]

def sortFn(value):
    return len(value)

items.sort(key=sortFn, reverse=True)
# ['elephant', 'duck', 'cow']