This shows you the differences between two versions of the page.
ic:laboratoare:01 [2020/09/26 18:26] acosmin.maria |
ic:laboratoare:01 [2020/10/18 15:55] (current) acosmin.maria |
||
---|---|---|---|
Line 3: | Line 3: | ||
==== Python3 Crash Course ==== | ==== Python3 Crash Course ==== | ||
- | === Command Line (CLI) === | + | Check-out this tutorial: [[ic:resurse:python|here]]. |
- | + | ||
- | Start REPL (Read Execute Print Repeat) interactive mode and exit: | + | |
- | <code python> | + | |
- | shell$ python # OR python3 | + | |
- | >>> | + | |
- | >>> quit() # OR CTRL-D | + | |
- | shell$ | + | |
- | </code> | + | |
- | + | ||
- | Execute files: | + | |
- | <code python> | + | |
- | shell$ python filename.py | + | |
- | shell$ python filename.py arg1 arg2 arg3 | + | |
- | </code> | + | |
- | + | ||
- | === Basics === | + | |
- | + | ||
- | Variables: | + | |
- | <code python> | + | |
- | a = 10 # int | + | |
- | b = 10.2 # float | + | |
- | c = "Ana are mere" # str | + | |
- | </code> | + | |
- | + | ||
- | Functions: | + | |
- | <code python> | + | |
- | def my_function(a, b): | + | |
- | return a + b | + | |
- | </code> | + | |
- | + | ||
- | In Python we don't use curly braces. Instead, we use indentation to define the scope of a function / statement. | + | |
- | + | ||
- | === Input/Output === | + | |
- | + | ||
- | <code python> | + | |
- | input_text = input() # type str | + | |
- | print(input_text) | + | |
- | + | ||
- | a = input() # 10 | + | |
- | a = int(a) # convert to int | + | |
- | print(a + 5) # 15 | + | |
- | </code> | + | |
- | + | ||
- | === Operators & Flow Control === | + | |
- | + | ||
- | * +, +=, -, -=, *, *=, /, /= | + | |
- | * ==, !=, <, <=, >, >= | + | |
- | + | ||
- | <code python> | + | |
- | a = 7 // 2 # a = 3 (Integer Division) | + | |
- | b = 7 % 2 # b = 1 (Modulus) | + | |
- | c = 2 ** 3 # c = 8 (Exponent) | + | |
- | d = 1 ^ 0 # d = 1 (XOR operation) | + | |
- | </code> | + | |
- | + | ||
- | === Conditional Statements & Loops === | + | |
- | <code python> | + | |
- | + | ||
- | i = 0 | + | |
- | cnt = 0 | + | |
- | while i < 100: | + | |
- | if i < 50: | + | |
- | cnt += 1 | + | |
- | if i % 2 == 0: | + | |
- | cnt *= 2 | + | |
- | elif i >= 50 and cnt % 2 == 0: | + | |
- | cnt += 3 | + | |
- | else: | + | |
- | cnt += 2 | + | |
- | if i > 90 or cnt > 200: | + | |
- | break | + | |
- | </code> | + | |
==== Encoding vs Encryption ==== | ==== Encoding vs Encryption ==== | ||
Line 82: | Line 9: | ||
=== Encoding === | === Encoding === | ||
- | * Used to transform data into another format. | + | * Is used to transform data from one format to another. |
- | * Typically used for data transfer between different systems. | + | * Typically used in data transfer between different systems. |
* The information is NOT kept secret!!! | * The information is NOT kept secret!!! | ||
* To decode the encoded data you only need to know the algorithm that was used. | * To decode the encoded data you only need to know the algorithm that was used. | ||
Line 93: | Line 20: | ||
* Types: Private-Key Encryption vs Public-Key Encryption (upcoming) | * Types: Private-Key Encryption vs Public-Key Encryption (upcoming) | ||
- | During the labs you will often need to convert data from one format to another. The most used data formats are the following: | + | During the labs, you will often need to convert data from one format to another. The most used data formats are the following: |
* ASCII (text) | * ASCII (text) | ||
* Binary (01010101) | * Binary (01010101) | ||
* Hexadecimal [0-9a-fA-F] | * Hexadecimal [0-9a-fA-F] | ||
- | * Base64 [a-Az-Z0-9] and '+' and '/'. Base64 usually ends in '=' or '=='. Used A LOT in Web because HTTP is a text transfer protocol. | + | * Base64 [a-Az-Z0-9] with '+' and '/'. Base64 usually ends in '=' or '=='. Used A LOT in Web because HTTP is a text transfer protocol. |
- | + | ||
- | Finally, here you have some conversion functions: | + | |
+ | Finally, here you have some useful conversion functions and XOR operations for different data formats: | ||
<file python utils.py> | <file python utils.py> | ||
import base64 | import base64 | ||
+ | |||
# CONVERSION FUNCTIONS | # CONVERSION FUNCTIONS | ||
+ | |||
def _chunks(string, chunk_size): | def _chunks(string, chunk_size): | ||
for i in range(0, len(string), chunk_size): | for i in range(0, len(string), chunk_size): | ||
yield string[i:i+chunk_size] | yield string[i:i+chunk_size] | ||
- | + | ||
+ | def _hex(x): | ||
+ | return format(x, '02x') | ||
+ | |||
def hex_2_bin(data): | def hex_2_bin(data): | ||
return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | ||
- | + | ||
def str_2_bin(data): | def str_2_bin(data): | ||
return ''.join(f'{ord(c):08b}' for c in data) | return ''.join(f'{ord(c):08b}' for c in data) | ||
- | + | ||
def bin_2_hex(data): | def bin_2_hex(data): | ||
return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) | return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) | ||
- | + | ||
def str_2_hex(data): | def str_2_hex(data): | ||
return ''.join(f'{ord(c):02x}' for c in data) | return ''.join(f'{ord(c):02x}' for c in data) | ||
- | + | ||
def bin_2_str(data): | def bin_2_str(data): | ||
return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | ||
- | + | ||
def hex_2_str(data): | def hex_2_str(data): | ||
return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | ||
- | + | ||
+ | # XOR FUNCTIONS | ||
+ | |||
+ | def strxor(a, b): # xor two strings, trims the longer input | ||
+ | return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) | ||
+ | |||
+ | def bitxor(a, b): # xor two bit-strings, trims the longer input | ||
+ | return ''.join(str(int(x) ^ int(y)) for (x, y) in zip(a, b)) | ||
+ | |||
+ | def hexxor(a, b): # xor two hex-strings, trims the longer input | ||
+ | return ''.join(_hex(int(x, 16) ^ int(y, 16)) for (x, y) in zip(_chunks(a, 2), _chunks(b, 2))) | ||
+ | |||
# BASE64 FUNCTIONS | # BASE64 FUNCTIONS | ||
+ | |||
def b64decode(data): | def b64decode(data): | ||
return bytes_to_string(base64.b64decode(string_to_bytes(data))) | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | ||
- | + | ||
def b64encode(data): | def b64encode(data): | ||
return bytes_to_string(base64.b64encode(string_to_bytes(data))) | return bytes_to_string(base64.b64encode(string_to_bytes(data))) | ||
- | + | ||
# PYTHON3 'BYTES' FUNCTIONS | # PYTHON3 'BYTES' FUNCTIONS | ||
+ | |||
def bytes_to_string(bytes_data): | def bytes_to_string(bytes_data): | ||
return bytes_data.decode() # default utf-8 | return bytes_data.decode() # default utf-8 | ||
- | + | ||
def string_to_bytes(string_data): | def string_to_bytes(string_data): | ||
return string_data.encode() # default utf-8 | return string_data.encode() # default utf-8 | ||
- | |||
</file> | </file> | ||
- | |||
- | === Exercise #1 === | ||
- | Decode the following strings: | ||
- | <code> | ||
- | C1 = 010101100110000101101100011010000110000101101100011011000110000100100001 | ||
- | C2 = 526f636b2c2050617065722c2053636973736f727321 | ||
- | C3 = WW91IGRvbid0IG5lZWQgYSBrZXkgdG8gZW5jb2RlIGRhdGEu | ||
- | </code> | ||
== Bytes in Python == | == Bytes in Python == | ||
Line 172: | Line 93: | ||
type(text2) # <class 'bytes'> | type(text2) # <class 'bytes'> | ||
</code> | </code> | ||
- | Both texts store basically the same information. The difference is in how the data is internally 'encoded' into 2 different object types. During the labs we will mostly work with str types, but some external libraries may require to transform the data from the string representation to a bytes object. | + | Both texts store basically the same information. The difference is in how the data is internally 'encoded' into 2 different object types. During the labs, we will mostly work with str types, but some external libraries may require to transform the data from the string representation to a bytes object. |
+ | === Exercise #1 - Encoding is nice === | ||
+ | Decode the following strings: | ||
+ | <code python> | ||
+ | C1 = "010101100110000101101100011010000110000101101100011011000110000100100001" | ||
+ | C2 = "526f636b2c2050617065722c2053636973736f727321" | ||
+ | C3 = "WW91IGRvbid0IG5lZWQgYSBrZXkgdG8gZW5jb2RlIGRhdGEu" | ||
+ | </code> | ||
+ | === Exercise #2 - But XOR-ing is cool === | ||
+ | |||
+ | Find the plaintext messages for the following ciphertexts knowing that the cipher is the XOR operation (ciphertext = plaintext XOR key) and the key is "abcdefghijkl". | ||
+ | <code python> | ||
+ | C1 = "000100010001000000001100000000110001011100000111000010100000100100011101000001010001100100000101" | ||
+ | C2 = "02030F07100A061C060B1909" | ||
+ | </code> | ||
==== Hail, Caesar! ==== | ==== Hail, Caesar! ==== | ||
- | === Exercise #2 === | + | === Encrypting a letter === |
- | In this first recipe we'll see how to encrypt a single letter using Caesar's cipher. | + | Let's start with a simple one: |
<code python> | <code python> | ||
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
Line 193: | Line 128: | ||
Create a new file named caesar.py containing the code above. To test the code, open the interpreter and try the following: | Create a new file named caesar.py containing the code above. To test the code, open the interpreter and try the following: | ||
- | |||
<code python> | <code python> | ||
- | linux$ python | + | shell$ python |
>>> from caesar import * | >>> from caesar import * | ||
- | >>> print alphabet | + | >>> print(alphabet) |
>>> alphabet[0] | >>> alphabet[0] | ||
>>> ord('A') | >>> ord('A') | ||
>>> len(alphabet) | >>> len(alphabet) | ||
>>> ord('D') - ord('A') | >>> ord('D') - ord('A') | ||
+ | >>> 26 % 26 | ||
>>> 28 % 26 | >>> 28 % 26 | ||
>>> -1 % 26 | >>> -1 % 26 | ||
Line 209: | Line 144: | ||
</code> | </code> | ||
- | === Python Exercise #1 - Decrypting a letter === | + | === Exercise #3 - Decrypting a letter === |
- | Add a ''caesar_dec'' function to ''caesar.py'', which decrypts a single letter encrypted using Caesar's cipher. | + | |
- | === Python Recipe #2 - Encrypting a string === | + | Add a 'caesar_dec' function to 'caesar.py', which decrypts a single letter encrypted using Caesar's cipher. |
- | We'll now expand our function to take string as input. First, a little intro to Python ''for'' loops. Some examples: | + | === Encrypting a string === |
- | + | ||
- | <code python> | + | |
- | for i in [1, 2, 3]: | + | |
- | print i | + | |
- | </code> | + | |
- | + | ||
- | In this first example, ''i'' iterates through the values in list ''[1, 2, 3]''. The code is similar to the classical **C** code: | + | |
- | + | ||
- | <code C> | + | |
- | for (int i = 1; i <= 3; i++) { | + | |
- | printf("%d\n", i); | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | To create lists of integers use the ''range'' function: | + | |
- | + | ||
- | <code python> | + | |
- | for i in range(3): | + | |
- | print i | + | |
- | </code> | + | |
- | + | ||
- | Execute the code above in the interpreter. Using the range function with two parameters, change the above to print integers 1 through 10. | + | |
- | + | ||
- | In Python, ''for'' can iterate through multiple types of objects, including strings. Try the below: | + | |
- | + | ||
- | <code python> | + | |
- | for letter in 'ABCD': | + | |
- | print letter | + | |
- | </code> | + | |
- | + | ||
- | We will now use the ''for'' instruction to expand our ''caesar_enc'' function: | + | |
+ | We'll now expand our function to take strings as input. | ||
<code Python> | <code Python> | ||
alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ' | alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
Line 254: | Line 158: | ||
ciphertext = ciphertext + caesar_enc(letter) | ciphertext = ciphertext + caesar_enc(letter) | ||
return ciphertext | return ciphertext | ||
- | | ||
</code> | </code> | ||
- | Test the above by starting a new interpreter; this time, we'll skip the ''from caesar import *'' part by running the source file before starting interactive mode: | + | Test the above by starting a new interpreter: |
<code Python> | <code Python> | ||
linux$ python -i caesar.py | linux$ python -i caesar.py | ||
Line 266: | Line 168: | ||
</code> | </code> | ||
- | Another way to run things, which can be very useful in general is to use a main() function and write your program script as follows: | + | Another way to run things, which can be very useful in general, is to use a main() function and write your program script as follows: |
- | + | <file Python test_caesar.py> | |
- | <code Python 'test_caesar.py'> | + | |
- | import sys | + | |
- | import random | + | |
- | import string | + | |
- | import operator | + | |
alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ' | alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
def caesar_enc(letter): | def caesar_enc(letter): | ||
- | if letter < 'A' or letter > 'Z': | + | if letter < 'A' or letter > 'Z': |
- | print 'Invalid letter' | + | print('Invalid letter') |
- | return | + | return |
- | else: | + | else: |
- | return alphabet[(ord(letter) - ord('A') + 3) % len(alphabet)] | + | return alphabet[(ord(letter) - ord('A') + 3) % len(alphabet)] |
def caesar_enc_string(plaintext): | def caesar_enc_string(plaintext): | ||
Line 290: | Line 186: | ||
def main(): | def main(): | ||
- | m = 'BINEATIVENIT' | + | m = 'BINEATIVENIT' |
- | c = caesar_enc_string(m) | + | c = caesar_enc_string(m) |
- | print c | + | print(c) |
| | ||
- | |||
if __name__ == "__main__": | if __name__ == "__main__": | ||
- | main() | + | main() |
- | </code> | + | </file> |
Then you can simply run the program, or type the following in a terminal: | Then you can simply run the program, or type the following in a terminal: | ||
Line 304: | Line 199: | ||
</code> | </code> | ||
+ | === Exercise #4 - Decrypting a string === | ||
- | === Python Exercise #2 - Decrypting a string === | + | Add the corresponding 'caesar_dec_string' function. |
- | + | ||
- | Add the corresponding ''caesar_dec_string'' function. | + | |
- | + | ||
- | === Python Recipe #3 - Shift ciphers === | + | |
- | + | ||
- | Python allows passing default values to parameters: | + | |
- | + | ||
- | <code Python> | + | |
- | def foo(a, b = 3): | + | |
- | print a, b | + | |
- | </code> | + | |
- | <code Python> | + | |
- | >>> foo(1) | + | |
- | >>> foo(1, 2) | + | |
- | </code> | + | |
- | We can use default parameter values to expand our ''caesar_enc'' function to take the key as an additional parameter, without breaking compatibility with our previous code. | + | === Shift ciphers === |
+ | Python allows passing default values to parameters. We can use default parameter values to expand our 'caesar_enc' function to take the key as an additional parameter, without breaking compatibility with our previous code. | ||
<code Python> | <code Python> | ||
def caesar_enc(letter, k = 3): | def caesar_enc(letter, k = 3): | ||
if letter < 'A' or letter > 'Z': | if letter < 'A' or letter > 'Z': | ||
- | print 'Invalid letter' | + | print('Invalid letter') |
return None | return None | ||
else: | else: | ||
Line 341: | Line 223: | ||
To test the new functions, try the below: | To test the new functions, try the below: | ||
<code Python> | <code Python> | ||
- | linux$ python -i caesar.py | + | shell$ python -i caesar.py |
>>> caesar_enc_string('HELLO') | >>> caesar_enc_string('HELLO') | ||
>>> caesar_enc_string('HELLO', 0) | >>> caesar_enc_string('HELLO', 0) | ||
Line 347: | Line 229: | ||
</code> | </code> | ||
- | + | === Exercise #5 - Shift Ciphers === | |
- | === Python Exercise #3 - Shift ciphers === | + | |
Using default parameters, expand your shift cipher decryption functions to support arbitrary keys. | Using default parameters, expand your shift cipher decryption functions to support arbitrary keys. | ||
- | === Python Recipe #4 - Format conversions === | + | ==== Bonus Exercise - Many-Time Pad ==== |
+ | Decrypt the last ciphertext knowing that all the messages were encrypted with the same key using OTP. | ||
+ | <file txt ciphers.ciphertexts> | ||
+ | 8841a58f876901c9e195d1e320e0c30a017bec11b0643d30533adcb0475e85a820d64e1a0869963453b490933b7005839f7d8a9571c8a890d75773bc2acc11d5cb3259f0610e95ad6ae1ec8445fc836b661b9c0554494c430210989e4a42ff7b4c19338945a68653c89d783e8460935c93896a3d73d9bc84a8e381951443ab8ada62c5d662d43c0da848c3602d | ||
+ | 8e14e681d0651cd5fb99d1a87cee972b4436fe19b22c3d1e7a75c2a6155ac4fa06d74e07042889300ab490d226614c818574d99a38d8a899d45478f83cca04818a3549f061079bb139a5f78542eac63873499513460d48534345addf5f42b632475623d14fb49c16c1913d7fca019f59d09b253c3c98a480e1e3829c0942bec2da478bcc6bd42a00e953883a622497 | ||
+ | e332a0cad0610ad6e691c6b967ad90634c73ec04fe216e586272dcb0474f98b336de5252042895310ab48c93277d4089d061968e76cbe194da0174f97dc512cd8e7b59bf351a8dad39acfdcb04edc62a275695045c4e405f4910bb9e4746f27915541fc653b5c81ec09f7f22ca2d945c9c916a2a7397bc8da4add1990945b7869c4a969a7a9c3f06a846882c6c28d6f9e6255ec96dd0b50e378054b2c89f6ee255312d330508e9cf4d43db | ||
+ | 8812e8c6842612d5a895c3a87ca28230557fe717fe2b75117571d0ad475985ae3bd04550041d8e2744a5d5ca3c60058e9e6c96db379ceb90df427df9338850c7842948a6701dd0e853b4eb9f45ffc6386e56800c5001095d4544ad934e06f3385d1f25c243a9c653eed23b3983239a589ec23d206891e882a2e3869b1445bbc38907c2d461d42c0dfb57c7207e2adbfeaa2a4bc37ccceb5777cf36f58f8776a75b242a7d105babd2564d959b79b8bd | ||
+ | 8008ac83d06f079cfbd0dba27aee89365262a904b62d740a3679dab01305cabb3ed30b0a4c2c92270aa581d227660586827dd99e2eddeb8cda5836e835c150d28a3648fe352698e878afe19f0df7882c2b1b9914125e09500c52b08b0b5db63a5e13348952af891d8f9f37229e609254868b26206698bc85a2ad82d3465cbccf9d4396c922d42d01e644cd6e7424ccb7b12c518d6d9faf166fc538bacc947fb149773f7c444ae7c95609959776b9e028502e45e0f6186c4fa51f4c80834f373d1f0b6130b770b6e1ce87 | ||
+ | 9603efdd952614d4e69ed4ed66af95260171e61cba687b0a797795a4154893fa33cc0b094125977b0a8f9ac673745782d074968c76d3e6d8d14e7af8628438c0833a45b1351b9bbd6daef69849be9f2e6653dc4042425b425810a99e474bb7325b0566c048e79c1bcad23f308725dd1db9c52769689ca480a4ad96d41f58a786974a8c942ebd7904e407db2b632f99eea9361fd976d2a25771c574ab81 | ||
+ | 8907a18f9d671a9bfa95c5a86aabcf634072fc50b1686f177778d4e3174583b433910b2e4820953415f6a5df3a7b44c7936dd9983383a8bbc34c36ff288413c4c77b4ea5350c9be86db3fd8910f7836a277a9101544c4411455ead9a474fa075153e27c006aa891a8f803d218f24941d93976a3b7398aa8deda29895481793c58f46c2d66bd43f1afd49cb2f606bc9f2e6255ad87cdeb4036bc138abcad76ead5b232e3d446ee2cf190c8d9b76a8b37b5e7c0bf6b71d7d42a50105c1964739224a1230 | ||
+ | 960ea9dbd04e169bec9fd0be2ea790635263fb00ac216e11787d9bed47618ffa35d65d1b57699a3b45a29dd62135428e966cd8db14c9fcd8c2497fef7dc319c79f7b44a3350b97ae7fa4ea8e0beac865274c9801410d6e5e4810be965d4fa07b5c0566e14faa9b16c3947671be28941db88d393d3cb1a181bea69d924654bdcb9f58c2ce61d43407e1498827636bcdffa3634cda76d6ab127d8068badd8363ec | ||
+ | b952fa8f836e1cccfbd0c0bd2ebd8c635925bb50bf3a78587c6fc6b74768b9991bf60b1d4c28893449a290c120355688d074908f33cee994da5836e835c150d29f2944be724f86fc2be1f19845f0893f27499117154f50454910bf90590ae17b461966c543b3cf008f95377188219256d083242d3c95a783a6e390804658a7d4da588adf62983d07ec42882f6a2ad0f9e8 | ||
+ | a20cbb9f953f1083e283dfba3afbcf6e142fff1aa964304c606edffa574286f7608248121622916118e28580637348cb982dc9936581bd8edb0d31ff2ec2038cdf37168b385bceba2ab5fb9e48f9952c3c57833d120d6a6375608db07469871d4e3823df43b5bd00cabd16149e299c58a0a30e2672b4bd80b9aa8198037ab7d5894a85df7dd57f15 | ||
+ | </file> | ||
- | === Python Exercise #4 - Format conversions === | + | == Hints == |
+ | <note tip> | ||
+ | * What happens when you XOR a character from [a-z] with the character ' ' (spacebar). Check also for [A-Z]. | ||
+ | * You can't write a perfect algorithm to solve the problem in one shot, you will mostly have to guess. Why? | ||
+ | * The challenge is nice, but can get tedious. Luckily for us there is a cool open source implementation. Check it out! | ||
+ | </note> | ||
+ | === How to run on Windows === | ||
+ | |||
+ | - Activate WSL (Windows Linux Subsystem) [[https://docs.microsoft.com/en-us/windows/wsl/install-win10|here]] | ||
+ | - turn Windows features on/off | ||
+ | - activate "Containers" and "Windows Subsystem for Linux" | ||
+ | - restart and install Ubuntu from Windows Store | ||
+ | - open a terminal and type ubuntu | ||
+ | - wait for the installation to complete and don't forget to "sudo apt-get upgrade", "sudo apt-get update" | ||
+ | - check that you have python3 installed and pip (python package manager) | ||
+ | - pip install urwid | ||
+ | - git clone https://github.com/cosminacho/MTP.git | ||
+ | - python mtp.py <ciphertexts filename> | ||
- | Find the plaintext messages for the following ciphertexts knowing that the cipher is the XOR operation (ciphertext = plaintext XOR key) and the key is "abcdefghijkl". | ||
- | <code> | ||
- | C1 = "000100010001000000001100000000110001011100000111000010100000100100011101000001010001100100000101" | ||
- | C2 = "02030F07100A061C060B1909" | ||
- | </code> | ||
- | <hidden> | ||
- | The solution is {{:ic:laboratoare:lab1_sol.zip|here}}. | ||
- | </hidden> |