diff --git a/2021/12/24/Endianness/index.html b/2021/12/24/Endianness/index.html index 363d7b8..2360db8 100644 --- a/2021/12/24/Endianness/index.html +++ b/2021/12/24/Endianness/index.html @@ -456,14 +456,14 @@
Problem 1 Solution
Start with the augmented matrix of the system, do row reduction like below
+Start with the augmented matrix of the system, and do row reduction like the below
\[
\left[\begin{array}{ccc|c}1&2&3&16\\2&0&-2&14\\3&2&1&3a\end{array}\right]\sim
\left[\begin{array}{ccc|c}1&2&3&16\\0&-4&-8&-18\\0&-4&-8&3a-48\end{array}\right]\sim
@@ -380,13 +380,11 @@ Problem 2 Solution According to the properties of determinants: Let A be a square matrix. First review the properties of determinants: Also since \(\det A^T=\det A\), a row operation on \(A^T\) amounts to a column operation on \(A\). The above property is true for column operations as well. With these properties in mind, we can do the following \[\begin{align}
@@ -403,7 +401,7 @@ Problem 3 Solution Denote \(A=BCB^{-1}\), it can be seen that \[\det A=\det BCB^{-1}=\det B\det C\det B^{-1}=\det (BB^{-1})\det C=\det C\] Denote \(A=BCB^{-1}\), it can be seen that \[\det A=\det (BCB^{-1})=\det B\det C\det B^{-1}=\det (BB^{-1})\det C=\det C\] Thus we can directly write down the determinant calculation process like below (applying row operations) \[
\begin{vmatrix}1&2&3\\1&4&5\\-1&3&7\end{vmatrix}=
\begin{vmatrix}1&2&3\\0&2&2\\0&5&10\end{vmatrix}=
@@ -433,7 +431,7 @@ Note the trace of a square matrix \(A\) is the sum of the diagonal entries in A and is denoted by tr \(A\). Remember the formula for inverse matrix \[
-A^{-1}=\frac{1}{\det A}\text{adj}\;A=[b_{ij}]\\
+A^{-1}=\frac{1}{\det A}\text{adj}\;A=[b_{ij}]\qquad
b_{ij}=\frac{C_{ji}}{\det A}\qquad C_{ji}=(-1)^{i+j}\det A_{ji}
\] Where \(\text{adj}\;A\) is the adjugate of \(A\), \(C_{ji}\) is a cofactor of \(A\), and \(A_{ji}\) denotes the submatrix of \(A\) formed by deleting row \(j\) and column \(i\). Now we can find the answer step-by-step: Problem 7 Solution First do row reduction to get row echelon form of the matrix \(A\): \[\begin{align}
+&\begin{bmatrix}1&2&2&10&3\\2&4&1&11&5\\3&6&2&18&1\end{bmatrix}\sim
+\begin{bmatrix}1&2&2&10&3\\0&0&-3&-9&-1\\0&0&-4&-12&-8\end{bmatrix}\sim
+\begin{bmatrix}1&2&2&10&3\\0&0&3&9&1\\0&0&1&3&2\end{bmatrix}\\
+\sim&\begin{bmatrix}1&2&2&10&3\\0&0&3&9&1\\0&0&3&9&6\end{bmatrix}
+\sim\begin{bmatrix}\color{fuchsia}{1}&2&2&10&3\\0&0&\color{fuchsia}{3}&9&1\\0&0&0&0&\color{fuchsia}{5}\end{bmatrix}
+\end{align}\] This shows that there are 3 pivot elements and 3 corresponding pivot columns (from the original matrix \(A\)) shown below \[\begin{Bmatrix}
+\begin{bmatrix}1\\2\\3\end{bmatrix},
+\begin{bmatrix}2\\1\\2\end{bmatrix},
+\begin{bmatrix}3\\5\\1\end{bmatrix}
+\end{Bmatrix}\] These columns form a basis for \(\text{Col}\;A\). Now look at the statements A and E. In the statement A, the first vector equals the sum of the first two pivot columns above. In the statement E, the third vector equals the sum of the last two pivot columns above. So both are TRUE. To check the statements B, C, and D, we need to find the basis for \(\text{Nul}\;A\). From the row echelon form, it can be deduced that with \(x_2\) and \(x_4\) as free variable \[\begin{align}
+x_5&=0\\x_3&=-3x_4\\x_1&=-2x_2-2x_3-10x_4=-2x_2-4x_4
+\end{align}\] This leads to \[
+\begin{bmatrix}x_1\\x_2\\x_3\\x_4\\x_5\end{bmatrix}=
+\begin{bmatrix}-2x_2-4x_4\\x_2\\-3x_4\\x_4\\0\end{bmatrix}=
+x_2\begin{bmatrix}-2\\1\\0\\0\\0\end{bmatrix}+x_4\begin{bmatrix}-4\\0\\-3\\1\\0\end{bmatrix}
+\] So the basis of \(\text{Nul}\;A\) is \[\begin{Bmatrix}
+\begin{bmatrix}-2\\1\\0\\0\\0\end{bmatrix},
+\begin{bmatrix}-4\\0\\-3\\1\\0\end{bmatrix}
+\end{Bmatrix}\] The statement B is TRUE because its first vector is the first column above scaled by 2, and its 2nd vector is just the 2nd column above scaled by -1. For statement D, its 1st vector is the same as the first column above, and the 2nd vector is just the sum of the two columns. It is TRUE as well. The statement B is FALSE since generating the 2nd vector with 3 and -2 coexisting is impossible. So the answer is C. Problem 9 Solution To find the \(\text{Ker}(T)\), need to find the set of \(p(t)\) such that \(T(p(t))=0\) \[
+T(a_0+a_{1}t+a_{2}t^2)=a_{2}t^3=0 \Rightarrow a_2=0
+\] Thus \(p(t)=a_0+a_{1}t\), the basis is \({1,t}\). So the answer is A. Problem 11 Solution The vector set can be regarded as a linear transformation, then we can do row reduction with it: \[
+\begin{bmatrix}1&1&1&1&1\\-1&1&2&0&-2\\1&1&1&1&3\end{bmatrix}\sim
+\begin{bmatrix}\color{fuchsia}{1}&1&1&1&1\\0&\color{fuchsia}{2}&3&1&-1\\0&0&0&0&\color{fuchsia}{2}\end{bmatrix}
+\] So there are 3 pivot entries and the rank is 3. The pivot columns below form a basis for \(H\). \[\begin{Bmatrix}
+\begin{bmatrix}1\\-1\\1\end{bmatrix},
+\begin{bmatrix}1\\1\\1\end{bmatrix},
+\begin{bmatrix}1\\-2\\3\end{bmatrix}
+\end{Bmatrix}\] A is wrong as it has only 2 vectors and the rank is 2. For B, C, and D, their 3rd vectors can be generated with the linear combination of the first two vectors. So their ranks are also 2. E is equivalent to the basis above. Its second vector can be generated like below \[
+\begin{bmatrix}1\\-1\\1\end{bmatrix}+\begin{bmatrix}1\\1\\1\end{bmatrix}=
+\begin{bmatrix}2\\0\\2\end{bmatrix}=2\times \begin{bmatrix}1\\0\\1\end{bmatrix}
+\] So the answer is E. Problem 12 Solution Note this question asks which one is NOT in the subspace spanned by \(\pmb x\) and \(\pmb y\). A vector is in the subspace spanned by \(\pmb x\) and \(\pmb y\) if and only if it is a linear combination of \(\pmb x\) and \(\pmb y\). This also means that the augmented matrix \([\pmb x\;\pmb y \mid \pmb v]\) has solutions. Let's try vector from A. \[
+\left[\begin{array}{cc|c}2&1&4\\3&2&2\\1&1&1\end{array}\right]\sim
+\left[\begin{array}{cc|c}2&1&4\\3&2&2\\2&2&2\end{array}\right]\sim
+\left[\begin{array}{cc|c}2&1&4\\1&0&0\\0&1&-2\end{array}\right]\sim
+\left[\begin{array}{cc|c}2&0&6\\1&0&0\\0&1&-2\end{array}\right]\sim
+\] This gives inconsistent results for \(x_1\). This vector is NOT a linear combination of \(\pmb x\) and \(\pmb y\). We do not need to continue here. So the answer is A. Problem 13 Solution For 2 radians counter-clockwise rotation, the transformation matrix is written as \[A=\begin{bmatrix}\cos(2)&-\sin(2)\\\sin(2)&\cos(2)\end{bmatrix}\] To find the eigenvalues of this \(2\times 2\) matrix, need to solve the equation \(\det (A-\lambda I)=0\) \[
+\begin{vmatrix}\cos(2)-\lambda&\sin(2)\\-\sin(2)&\cos(2)-\lambda\end{vmatrix}=\lambda^2-2\cos(2)+\cos^2(2)+\sin^2(2)=\lambda^2-2\cos(2)+1
+\] Apply the quadratic formula, get the roots \[\lambda=\frac{2\cos(2)\pm\sqrt{4\cos^2(2)-4}}{2}=\cos(2)\pm i\sin(2)\] So the answer is C. Problem 18 Solution Remember Problem 6 introduced the definition of trace, which is the sum of all diagonal entries of a matrix. Denote the \(2\times 2\) as \(A=\begin{bmatrix}a&b\\c&d\end{bmatrix}\), then \(\text{tr}(A)=a+d=-2\). Since \(\det A=11\), it gives \(ad-bc=11\). With these in mind, we can do the eigenvalue calculation below \[
+\begin{vmatrix}a-\lambda&b\\c&d-\lambda\end{vmatrix}=\lambda^2-(a+d)\lambda+ad-bc=\lambda^2+2\lambda+11=0
+\] Apply the quadratic formula, get the roots \[\lambda=\frac{-2\pm\sqrt{4-44}}{2}=-1\pm i\sqrt{10}\] Refer to the following table for the mapping from \(2\times 2\) matrix eigenvalues to trajectories: So the answer is C.Problem 2
-
-
-a. If a multiple of one row of \(A\) is added to another row to produce a matrix \(B\),then \(\det B =\det A\).
+
+>Let A be a square matrix.
+> a. If a multiple of one row of \(A\) is added to another row to produce a matrix \(B\), then \(\det B =\det A\).
b. If two rows of \(A\) are interchanged to produce \(B\), then \(\det B=-\det A\).
c. If one row of A is multiplied by \(k\) to produce B, then \(\det B=k\cdot\det A\).Problem 3
-Problem 6
Problem 7
-
+Problem 8
@@ -471,7 +499,10 @@ Problem 9
-
+Problem 10
@@ -485,21 +516,46 @@ Problem 11
-
+Problem 12
-
+Problem 13
-
+Problem 14
@@ -534,7 +590,48 @@ Problem 18
-
+
+
+
+
+
+
+
+
+
+Eigenvalues
+Trajectories
+
+
+\(\lambda_1>0, \lambda_2>0\)
+Repeller/Source
+
+
+\(\lambda_1<0, \lambda_2<0\)
+Attactor/Sink
+
+
+\(\lambda_1<0, \lambda_2>0\)
+Saddle Point
+
+
+\(\lambda = a\pm bi, a>0\)
+Spiral (outward) Point
+
+
+\(\lambda = a\pm bi, a<0\)
+Spiral (inward) Point
+
+
+
+\(\lambda = \pm bi\)
+Ellipses (circles if \(b=1\))
+Problem 19
@@ -694,14 +791,14 @@ Other MA265 Final Exam Solutions
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
PacketMania
Symbols count total:
- 436k
+ 441k
<p>Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices).
@@ -370,10 +370,10 @@
DIY Projects
Symbols count total:
- 436k
+ 441k
Study Notes
Symbols count total:
- 436k
+ 441k
Study Notes
Symbols count total:
- 436k
+ 441k
Technical Know-how
Symbols count total:
- 436k
+ 441k
Technology Review
Symbols count total:
- 436k
+ 441k
Tool Guide
Symbols count total:
- 436k
+ 441k
Categories
Symbols count total:
- 436k
+ 441k
-
+
Memory access errors are the most common software errors that often cause program crashes. The AddressSanitizer tool, developed by Google engineers in 2012, has become the first choice of C/C++ programmers for its wide coverage, high efficiency, and low overhead. Here is a brief introduction to its principle and usage.
\nOne man's \"magic\" is another man's engineering. \"Supernatural\" is a null word.
— Robert Anson Heinlein (American science fiction author, aeronautical engineer, and naval officer)
The C/C++ language allows programmers to have low-level control over memory, and this direct memory management has made it possible to write efficient application software. However, this has also made memory access errors, including buffer overflows, accesses to freed memory, and memory leaks, a serious problem that must be coped with in program design and implementation. While there are tools and software that provide the ability to detect such errors, their operational efficiency, and functional coverage are often less than ideal.
\nIn 2012, Google engineer Konstantin Serebryany and team members released an open-source memory access error detector for C/C++ programs called AddressSanitizer1. AddressSanitizer (ASan) applies new memory allocation, mapping, and code stubbing techniques to detect almost all memory access errors efficiently. Using the SPEC 2006 benchmark analysis package, ASan runs with an average slowdown of less than 2 and memory consumption of about 2.4 times. In comparison, another well-known detection tool Valgrind has an average slowdown of 20, which makes it almost impossible to put into practice.
\nThe following table summarizes the types of memory access errors that ASan can detect for C/C++ programs:
\nError Type | \nAbbreviation | \nNotes | \n
---|---|---|
heap use after free | \nUAF | \nAccess freed memory (dangling pointer dereference) | \n
heap buffer overflow | \nHeap OOB | \nDynamic allocated memory out-of-bound read/write | \n
heap memory leak | \nHML | \nDynamic allocated memory not freed after use | \n
global buffer overflow | \nGlobal OOB | \nGlobal object out-of-bound read/write | \n
stack use after scope | \nUAS | \nLocal object out-of-scope access | \n
stack use after return | \nUAR | \nLocal object out-of-scope access after return | \n
stack buffer overflow | \nStack OOB | \nLocal object out-of-bound read/write | \n
ASan itself cannot detect heap memory leaks. But when ASan is integrated into the compiler, as it replaces the memory allocation/free functions, the original leak detection feature of the compiler tool is consolidated with ASan. So, adding the ASan option to the compilation command line also turns on the leak detection feature by default.
\nThis covers all common memory access errors except for \"uninitialized memory reads\" (UMR). ASan detects them with a false positive rate of 0, which is quite impressive. In addition, ASan detects several C++-specific memory access errors such as
\nnew foo[n]
, should not call delete foo
for deletion, use delete [] foo
instead.ASan's high reliability and performance have made it the preferred choice of compiler and IDE developers since its introduction. Today ASan is integrated into all four major compilation toolsets:
\nCompiler/IDE | \nFirst Support Version | \nOS | \nPlatform | \n
---|---|---|---|
Clang/LLVM2 | \n3.1 | \nUnix-like | \nCross-platform | \n
GCC | \n4.8 | \nUnix-like | \nCross-platform | \n
Xcode | \n7.0 | \nMac OS X | \nApple products | \n
MSVC | \n16.9 | \nWindows | \nIA-32, x86-64 and ARM | \n
ASan's developers first used the Chromium open-source browser for routine testing and found more than 300 memory access errors over 10 months. After integration into mainstream compilation tools, it reported long-hidden bugs in numerous popular open-source software, such as Mozilla Firefox, Perl, Vim, PHP, and MySQL. Interestingly, ASan also identified some memory access errors in the LLVM and GCC compilers' code. Now, many software companies have added ASan run to their mandatory quality control processes.
\nThe USENIX conference paper 3, published by Serebryany in 2012, comprehensively describes the design principles, algorithmic ideas, and programming implementation of ASan. In terms of the overall structure, ASan consists of two parts.
\nmalloc/free
and its related functions to create poisoned red zones at the edge of dynamically allocated heap memory regions, delay the reuse of memory regions after release, and generate error reports.Here shadow memory, compiler instrumentation, and memory allocation function replacement are all previously available techniques, so how has ASan innovatively applied them for efficient error detection? Let's take a look at the details.
\nMany inspection tools use separated shadow memory to record metadata about program memory, and then apply instrumentation to check the shadow memory during memory accesses to confirm that reads and writes are safe. The difference is that ASan uses a more efficient direct mapping shadow memory.
\nThe designers of ASan noted that typically the malloc
function returns a memory address that is at least 8-byte aligned. For example, a request for 20 bytes of memory would divide 24 bytes of memory, with the last 3 bits of the actual return pointer being all zeros. in addition, any aligned 8-byte sequence would only have 9 different states: the first \\(k\\,(0\\leq k \\leq 8)\\) bytes are accessible, and the last \\(8-k\\) are not. From this, they came up with a more compact shadow memory mapping and usage scheme:
Shadow = (Mem >> 3) + 0x20000000;
Shadow = (Mem >> 3) + 0x7fff8000;
The following figure shows the address space layout and mapping relationship of ASan. Pay attention to the Bad area in the middle, which is the address segment after the shadow memory itself is mapped. Because shadow memory is not visible to the application, ASan uses a page protection mechanism to make it inaccessible.
\nOnce the shadow memory design is determined, the implementation of compiler instrumentation to detect dynamic memory access errors is easy. For memory accesses of 8 bytes, the shadow memory bytes are checked by inserting instructions before the original read/write code, and an error is reported if they are not zero. For memory accesses of less than 8 bytes, the instrumentation is a bit more complicated, where the shadow memory byte values are compared with the last three bits of the read/write address. This situation is also known as the \"slow path\" and the sample code is as follows.
\n// Check the cases where we access first k bytes of the qword |
For global and stack (local) objects, ASan has designed different instrumentation to detect their out-of-bounds access errors. The red zone around a global object is added by the compiler at compile time and its address is passed to the runtime library at application startup, where the runtime library function then poisons the red zone and writes down the address needed in error reporting. The stack object is created at function call time, and accordingly, its red zone is created and poisoned at runtime. In addition, because the stack object is deleted when the function returns, the instrumentation code must also zero out the shadow memory it is mapped to.
\nIn practice, the ASan compiler instrumentation process is placed at the end of the compiler optimization pipeline so that instrumentation only applies to the remaining memory access instructions after variable and loop optimization. In the latest GCC distribution, the ASan compiler stubbing code is located in two files in the gcc subdirectory gcc/asan.[ch]
.
The runtime library needs to include code to manage shadow memory. The address segment to which shadow memory itself is mapped is to be initialized at application startup to disable access to shadow memory by other parts of the program. The runtime library replaces the old memory allocation and free functions and also adds some error reporting functions such as __asan_report_load8
.
The newly replaced memory allocation function malloc
will allocate additional storage as a red zone before and after the requested memory block and set the red zone to be non-addressable. This is called the poisoning process. In practice, because the memory allocator maintains a list of available memory corresponding to different object sizes, if the list of a certain object is empty, the OS will allocate a large set of memory blocks and their red zones at once. As a result, the red zones of the preceding and following memory blocks will be connected, as shown in the following figure, where \\(n\\) memory blocks require only \\(n+1\\) red zones to be allocated.
The new free
function needs to poison the entire storage area and place it in a quarantine queue after the memory is freed. This prevents the memory region from being allocated any time soon. Otherwise, if the memory region is reused immediately, there is no way to detect incorrect accesses to the recently freed memory. The size of the quarantine queue determines how long the memory region is in quarantine, and the larger it is the better its capability of detecting UAF errors!
By default, both the malloc
and free
functions log their call stacks to provide more detailed information in the error reports. The call stack for malloc
is kept in the red zone to the left of the allocated memory, so a large red zone can retain more call stack frames. The call stack for free
is stored at the beginning of the allocated memory region itself.
Integrated into the GCC compiler, the source code for the ASan runtime library replacement is located in the libsanitizer subdirectory libsanitizer/asan/*
, and the resulting runtime library is compiled as libasan.so
.
ASan is very easy to use. The following is an example of an Ubuntu Linux 20.4 + GCC 9.3.0 system running on an x86_64 virtual machine to demonstrate the ability to detect various memory access errors.
\nAs shown below, the test program writes seven functions, each introducing a different error type. The function names are cross-referenced with the error types one by one:
\n/* |
The test program calls the getopt
library function to support a single-letter command line option that allows the user to select the type of error to be tested. The command line option usage information is as follows.
\b$ ./asan-test |
The GCC compile command for the test program is simple, just add two compile options
\n-fsanitize=address
: activates the ASan tool-g
: enable debugging and keep debugging informationFor Heap OOB error, the run result is
\n$ ./asan-test -b |
Referring to the heap-buffer-overflow
function implementation, you can see that it requests 40 bytes of memory to hold 10 32-bit integers. However, on the return of the function, the code overruns to read the data after the allocated memory. As the above run log shows, the program detects a Heap OOB error and aborts immediately. ASan reports the name of the source file and line number asan-test.c:34
where the error occurred, and also accurately lists the original allocation function call stack for dynamically allocated memory. The \"SUMMARY\" section of the report also prints the shadow memory data corresponding to the address in question (observe the lines marked by =>
). The address to be read is 0x604000000038, whose mapped shadow memory address 0x0c087fff8007 holds the negative value 0xfa (poisoned and not addressable). Because of this, ASan reports an error and aborts the program.
The Stack OOB test case is shown below. ASan reports an out-of-bounds read error for a local object. Since the local variables are located in the stack space, the starting line number asan-test.c:37
of the function stack_buffr_overflow
is listed. Unlike the Heap OOB report, the shadow memory poisoning values for the front and back redzone of the local variable are different, with the previous Stack left redzone
being 0xf1 and the later Stack right redzone
being 0xf3. Using different poisoning values (both negative after 0x80) helps to quickly distinguish between the different error types.
$ ./asan-test -s |
The following Global OOB test result also clearly shows the error line asan-test.c:16
, the global variable name ga
and its definition code location asan-test.c:13:5
, and you can also see that the global object has a red zone poisoning value of 0xf9.
$ ./asan-test -o |
Note that in this example, the global array int ga[10] = {1};
is initialized, what happens if it is uninitialized? Change the code slightly
int ga[10]; |
Surprisingly, ASan does not report the obvious Global OOB error here. Why?
\nThe reason has to do with the way GCC treats global variables. The compiler treats functions and initialized variables as Strong symbols, while uninitialized variables are Weak symbols by default. Since the definition of weak symbols may vary from source file to source file, the size of the space required is unknown. The compiler cannot allocate space for weak symbols in the BSS segment, so it uses the COMMON block mechanism so that all weak symbols share a COMMON memory region, thus ASan cannot insert the red zone. During the linking process, after the linker reads all the input target files, it can determine the size of the weak symbols and allocate space for them in the BSS segment of the final output file.
\nFortunately, GCC's -fno-common
option turns off the COMMON block mechanism, allowing the compiler to add all uninitialized global variables directly to the BSS segment of the target file, also allowing ASan to work properly. This option also disables the linker from merging weak symbols, so the linker reports an error directly when it finds a compiled unit with duplicate global variables defined in the target file.
This is confirmed by a real test. Modify the GCC command line for the previous code segment
\ngcc asan-test.c -o asan-test -fsanitize=address -fno-common -g |
then compile, link, and run. ASan successfully reported the Global OOB error.
\nThe following is a running record of UAF error detection. Not only is the information about the code that went wrong reported here, but also the call stack of the original allocation and free functions of the dynamic memory is given. The log shows that the memory was allocated by asan-test.c:25
, freed at asan-test.c:27
, and yet read at asan-test.c:28
. The shadow memory data printed later indicates that the data filled is negative 0xfd, which is also the result of the poisoning of the memory after it is freed.
$ \u0007./asan-test -\bf |
The results of the memory leak test are as follows. Unlike the other test cases, ABORTING
is not printed at the end of the output record. This is because, by default, ASan only generates a memory leak report when the program terminates (process ends). If you want to check for leaks on the fly, you can call ASan's library function __lsan_do_recoverable_leak_check
, whose definition is located in the header file sanitizer/lsan_interface.h
.
$ ./asan-test -l |
See the stack_use_after_scope
function code, where the memory unit holding the local variable c
is written outside of its scope. The test log accurately reports the line number line 54
where the variable is defined and the location of the incorrect writing code asan-test.c:57
:
./asan-test -\bp |
The UAR test has its peculiarities. Because the stack memory of a function is reused immediately after it returns, to detect local object access errors after return, a \"pseudo-stack\" of dynamic memory allocation must be set up, for details check the relevant Wiki page of ASan4. Since this algorithm change has some performance impact, ASan does not detect UAR errors by default. If you really need to, you can set the environment variable ASAN_OPTIONS
to detect_stack_use_after_return=1
before running. The corresponding test logs are as follows.
$ export ASAN_OPTIONS=detect_stack_use_after_return=1 |
ASan supports many other compiler flags and runtime environment variable options to control and tune the functionality and scope of the tests. For those interested please refer to the ASan flags Wiki page5.
\nA zip archive of the complete test program is available for download here: asan-test.c.gz
\nSerebryany, K.; Bruening, D.; Potapenko, A.; Vyukov, D. \"AddressSanitizer: a fast address sanity checker\". In USENIX ATC, 2012↩︎
Here is a series of general study guides to college-level C programming courses. This is the first part covering compilation and linking, file operations, typedef, structures, string operations, basic pointer operations, etc.
\nWrite the command to compile a single C file named \"hello.c\" into an object file called \"hello.o\".
\ngcc -c hello.c -o hello.o
Write the command to link two object files named \"hello.o\" and \"goodbye.o\" into the executable called \"application\".
\ngcc hello.o goodbye.o -o application
Can you \"run\" an object file if it contains the \"main()\" function?
\nNo, an object file cannot be run directly. If you force it to run, it will exec format error
.
Can you \"run\" an executable that contains a single function called \"main()\"?
\nYes, an executable with just main() can be run.
Can you \"run\" an executable that does not contain a function called \"main()\"?
\nNo, main() is required to run an executable.
What does the \"-Wall\" flag do?
\n\"-Wall\" enables all compiler warnings
What does the \"-g\" flag do?
\n\"-g\" adds debugging information.
What does the \"-ansi\" flag do?
\n\"-ansi\" enables strict ANSI C mode. The \"-ansi\" flag is equivalent to the -\"std=c89\" flag.
What does the \"-c\" flag do?
\n\"-c\" compiles to object file only, does not link.
What does the \"-o\" flag do?
\n\"-o\" specifies output file name.
\nGiven the following FILE pointer variable definition, write the code that will open a file named \"hello.txt\" for read-only access and print a message of your choice if there was an error in doing so.
\nFILE *my_file = 0;
my_file = fopen("hello.txt", "r");
if (my_file = NULL) {
fprintf(stdout, "Failed to open the file\\n");
}
Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for read access. Put the code inside the 'if' predicate:
\nif (access("hello.txt", R_OK) == 0) {
/* Yes, we can open the file... */
}
Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for write access. Put the code inside the 'if' predicate:
\nif (access("hello.txt", W_OK) == 0) {
/* Yes, we can open the file... */
}
Write a function called read_and_print() that will do the following:
\nint read_and_print() {
char my_string[100];
my_int;
FILE *fp = fopen("hello.txt", "r");
if(!fp) return -1;
if (fscanf(fp, "%s", my_string) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
if (fscanf(fp, "%d", &my_int) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
printf("%s %d\\n", my_string, my_int);
fclose(fp);
fp = NULL;
return my_int;
}
Write a function named print_reverse that will open a text file named \"hello.txt\" and print each character in the file in reverse. i.e. print the first character last and the last character first. The function should return the number of characters in the file. Upon any error, return -1. HINT: Use fseek() a lot to do this.
\nint print_reverse(char* filename) {
FILE* fp = fopen(filename, "r");
if(!fp) return -1;
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
for (int i = size - 1; i >= 0; i--) {
fseek(fp, i, SEEK_SET);
char c = fgetc(fp);
printf("%c", c);
}
fclose(fp);
fp = NULL;
return size;
}
Write a function that defines a structure, initializes it, writes it to a file called \"struct.out\", closes the file, re-opens the file for read-only access, reads a single structure into a new struct variable, and then closes the file. Print the structure contents to the screen. On any error, return -1. Otherwise, return 0.
\n
struct Person {
char name[50];
int age;
};
int write_and_read_struct() {
struct Person p = { "John Doe", 30 };
// Write struct to file
FILE* fp = fopen("struct.out", "w");
if (!fp) return -1;
if (fwrite(&p, sizeof(struct Person), 1, fp) != 1) {
\tfclose(fp);
\tfp = NULL:
\treturn -1;
}
fclose(fp);
// Read struct from file
fp = fopen("struct.out", "r");
if (!fp) return -1;
struct Person p2;
if (fread(&p2, sizeof(struct Person), 1, fp) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
fclose(fp);
fp = NULL;
// Print struct
printf("Name: %s, Age: %d\\n", p2.name, p2,age);
return 0;
}
Declare a type called \"my_array_t\" that is an array of 15 floats.
\ntypedef float my_array_t[15];
Declare a type called \"struct_arr_t\" that is an array of 10 structs of the format
\nstruct str {
int x;
int y;
};
typedef struct str struct_arr_t[10];
Define a variable called my_str_arr of type struct_arr_type.
\nstruct_arr_t my_str_arr;
Can two elements within a structure have the same name?
\nNo, two elements cannot have the same name
Can you initialize a structure like this?
\nstruct my_str {
int x;
float y;
} mine = { 0, 0.0 };
Can you initialize a structure like this?
\nstruct my_str {
int x;
float y;
};
void my_func(int n) {
my_str mine = { n, 0.0 };
}struct str mine = { n, 0.0 };
instead.
Declare a structure that contains an integer element named i, a floating point element named f, and an array of 20 characters named str (in that order). Name it anything you want.
\nstruct mystruct {
int i;
float f;
char str[20];
};
Define a variable called \"my_new_struct\" of the type in the previous question.
\nstruct mystruct my_new_struct;
Define a variable called \"my_array_of_structs\" that is an array of 40 structures of the type in the prior two questions.
\nstruct mystruct my_array_of_structs[40];
Define a function called bigger_rectangle() that will accept one argument of the structure type rectangle (declared below) and will multiply the width dimension by 1.5, the height dimension by 2.5 and the length dimension by 3. The function should return the new structure. Define a temporary local variable if you want to.
\nstruct rectangle {
float height;
float width;
float length;
};
struct rectangle bigger_rectangle(struct rectangle r) {
struct rectangle bigger;
bigger.height = r.height * 2.5;
bigger.width = r.width * 1.5;
bigger.length = r.length * 3;
return bigger;
}
Write a function named sum_rectangles that will open a binary file named \"rect.in\" for reading and read the binary images of rectangle structures from it. For each rectangle structure, add its elements to those of the first structure read. e.g. sum the height fields of all the structures, sum the width fields of all the structures, etc... Return a structure from sum_rectangles where each element represents the sum of all structures read from the file. i.e. the height field should be the sum of all of the height fields of each of the structures. On any file error, return the structure { -1.0, -1.0, -1.0 }.
\n
struct rectangle {
float height;
float width;
float length;
};
struct rectangle sum_rectangles() {
struct rectangle bad_struct = {-1.0, -1.0, -1.0};
FILE *fp = fopen("rect.in", "rb");
if(!fp) {
return bad_struct;
}
struct rectangle sum = {0, 0, 0};
struct rectangle r;
if (fread(&r, sizeof(struct rectangle), 1, fp) != 1) {
fclose(fp);
fp = NULL;
return bad_struct;
}
sum.height = r.height;
sum.width = r.width;
sum.length = r.length;
while (fread(&r, sizeof(struct rectangle), 1, fp) == 1) {
sum.height += r.height;
sum.width += r.width;
sum.length += r.length;
}
fclose(fp);
fp = NULL;
return sum;
}
Under what circumstances would you place an assert() into your code?
\nUsed to check for logical errors and malformed data.
What will be the result of the following code:
\nint my_func() {
int count = 0;
int sum = 0;
for (count = 0; count < 100; count++) {
assert(sum > 0);
sum = sum + count;
}
return sum;
}
What might you do to the previous code to make it do a \"better\" job?
\nMove assert(sum > 0);
down, after for loop. Or change to assert(sum >= 0);
Write a function called do_compare() that will prompt the user for two strings of maximum length 100. It should compare them and print one of the following messages:
\nThe function should always return zero.
\n
int do_compare() {
char str1[101], str2[101];
// Prompt the user to enter two strings
printf("Enter the first string (up to 100 characters): ");
fgets(str1, sizeof(str1), stdin);
printf("Enter the second string (up to 100 characters): ");
fgets(str2, sizeof(str2), stdin);
// Compare the strings
int cmp = strcmp(str1, str2);
// Print the comparison result
if (cmp == 0) {
printf("The strings are equal.\\n");
} else if (cmp < 0) {
printf("The first string comes before the second.\\n");
} else {
printf("The second string comes before the first.\\n");
}
return 0;
}
What is the difference between initialization of a variable and assignment to a variable?
\nInitialization is giving a variable its initial value, typically at the time of declaration, while assignment is giving a new value to an already declared variable at any point after initialization.
What is the difference between a declaration and a definition?
\nDeclaration is announcing the properties of var (no memory allocation), definition is allocating storage for a var and initializing it.
What is the difference between a global variable and a local variable?
\nGlobal variables have a broader scope, longer lifetime, and higher visibility compared to local variables, which are limited to the scope of the function in which they are declared.
For the following questions, assume that the size of an 'int' is 4 bytes, the size of a 'char' is one byte, the size of a 'float' is 4 bytes, and the size of a 'double' is 8 bytes. Write the size of the following expressions:
\nstruct my_coord {
int x;
int y;
double altitude;
};
struct my_line {
struct my_coord first;
struct my_coord second;
char name[10];
};
struct my_coord var;
struct my_coord array[3];
struct my_line one_line;
struct my_line two_lines[2];
sizeof(struct my_coord) = __16___
\nsizeof(var) = __16___
\nsizeof(array[1]) = __16___
\nsizeof(array[2]) = __16___
\nsizeof(array) = __48___
\nsizeof(struct my_line) = __48___
\nsizeof(two_lines) = __96___
\nsizeof(one_line) = __48___
\nExplanation: When calculating the size of a struct, we need to consider alignment and padding, which can affect the overall size of the struct. In the case of struct my_line
, the total size is influenced by the alignment requirements of its members. The largest member of struct my_coord
is double altitude
, which is 8 bytes. This means that the double altitude
member will determine the alignment and padding for the entire struct my_coord
within struct my_line
.
So here char name[10];
will occupy (10 bytes) + (6 bytes padding to align char[10] on an 8-byte boundary). This ends up with (16+16+10+6) for the size of struct my_line
.
Remember that the size of the structure should be a multiple of the biggest variable.
Draw the memory layout of the prior four variables; var, array, one_line, and two_lines on a line of boxes. Label the start of each variable and clearly show how many bytes each element within each structure variable consumes.
Re-define the two_lines variable above and _initialize_ it's contents with the following values:
\nfirst my_line structure:
first my_coord structure:
x = 1
y = 3
altitude = 5.6
second my_coord structure:
x = 4
y = 5
altitude = 2.1
name = "My Town"
second my_line structure:
first my_coord structure:
x = 9
y = 2
altitude = 1.1
second my_coord structure:
x = 3
y = 3
altitude = 0.1
name = "Your Town"
struct my_line two_lines[2] = {
{
{1, 3, 5.6},
{4, 5, 2.1},
"My Town"
},
{
{9, 2, 1.1},
{3, 3, 0.1},
"Your Town"
}
};
How many bytes large is the following definition?
\nstruct my_coord new_array[] = { |
(4 + 4 + 8) * 3 = 48
What is printed by the following three pieces of code:
\nint x = 0; int x = 0; int x = 0;
int y = 0; int y = 0; int y = 0;
int *p = NULL; int *p = NULL; int *p = NULL;
int *q = NULL; int *q = NULL;
p = &x;
*p = 5; p = &x; p = &y;
p = &y; q = p; q = &x;
*p = 7; *q = 7; p = 2;
q = 3;
printf("%d %d\\n", x, y); printf("%d %d\\n", x, y); printf("%d %d\\n", x, y);
The 1st column code snippet printed 5 7
. The 1st column code snippet printed 7 0
. The 1st column code snippet printed 0 0
.
Consider the following variable definitions:
\nint x = 2; |
And assume that p is initialized to point to one of the integers in arr. Which of the following statements are legitimate? Why or why not?
\np = arr; arr = p; p = &arr[2]; p = arr[x]; p = &arr[x];
arr[x] = p; arr[p] = x; &arr[x] = p; p = &arr; x = *arr;
x = arr + x; p = arr + x; arr = p + x; x = &(arr+x); p++;
x = --p; x = *p++; x = (*p)++; arr++; x = p - arr;
x = (p>arr); arr[*p]=*p; *p++ = x; p = p + 1; arr = arr + 1;
Let's go through each statement to determine if it is legitimate or not, and explain:
\np = arr;
- Legitimate. Assigns the address of the first element of arr
to p
.arr = p;
- Not legitimate. You cannot assign to an array name.p = &arr[2];
- Legitimate. Assigns the address of arr[2]
to p
.p = arr[x];
- Not legitimate. arr[x]
is an integer value, not an address.p = &arr[x];
- Legitimate. Assigns the address of arr[x]
to p
.arr[x] = p;
- Not legitimate. arr[x]
is an integer value, not a pointer.arr[p] = x;
- Not legitimate. arr[p]
is not a valid operation. p
should be an index, not a pointer.&arr[x] = p;
- Not legitimate. You cannot assign a value to the address of an element.p = &arr;
- Not legitimate. &arr
is the address of the whole array, not a pointer to an integer.x = *arr;
- Legitimate. Assigns the value of the first element of arr
to x
.x = arr + x;
- Legitimate. Calculates the address of arr[x]
and assigns it to x
.p = arr + x;
- Legitimate. Calculates the address of arr[x]
and assigns it to p
.arr = p + x;
- Not legitimate. You cannot assign to an array name.x = &(arr+x);
- Not legitimate. &
expects an lvalue, but (arr+x)
is not an lvalue.p++;
- Legitimate. Increments the pointer p
to point to the next element.x = --p;
- Legitimate. Decrements p
and assigns its value to x
.x = *p++;
- Legitimate. Assigns the value pointed to by p
to x
, then increments p
.x = (*p)++;
- Legitimate. Assigns the value pointed to by p
to x
, then increments the value pointed to by p
.arr++;
- Not legitimate. You cannot increment the entire array arr
.x = p - arr;
- Legitimate. Calculates the difference in addresses between p
and arr
and assigns it to x
.x = (p>arr);
- Not legitimate. Comparison between a pointer and an array is not valid.arr[*p]=*p;
- Not legitimate. arr[*p]
is not a valid assignment target.*p++ = x;
- Legitimate. Assigns x
to the value pointed to by p
, then increments p
.p = p + 1;
- Legitimate. Increments the pointer p
to point to the next memory location.arr = arr + 1;
- Not legitimate. You cannot increment the entire array arr
.📝Notes: The difference between x = *p++;
and x = (*p)++;
lies in how the increment operator (++) is applied.
x = *p++;
This statement first dereferences the pointer p to get the value it points to, assigns that value to x and then increments the pointer p to point to the next element (not the value pointed to by p). So, x gets the value pointed to by p before the increment.x = (*p)++;
This statement first dereferences the pointer p to get the value it points to, assigns that value to x, and then increments the value pointed to by p. So, x gets the value pointed to by p before the increment, and the value at the memory location pointed to by p is incremented.Here's a brief example to illustrate the difference:
\n
int main() {
int array[] = {1, 2, 3};
int *p = array;
int x;
// x gets the value pointed to by p, then p is incremented
x = *p++; // x = 1, p now points to array[1]
printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
// x gets the value pointed to by p, then the value pointed to
// by p is incremented
x = (*p)++; // x = 2, array[1] is now 3
printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
return 0;
}
The output of the above program is
\nx = 1, array[1] = 2, p points to 2
x = 2, array[1] = 3, p points to 3
To test your understanding, now check the following code snippet, what will the output be:
\nint x = 2, y = 15, z = 0;
int *p = 0;
p = &y;
x = *p++;
printf("x = %d, y = %d, z = %d\\n", x, y, z);
p = &y;
z = (*p)++;
printf("x = %d, y = %d, z = %d\\n", x, y, z);
Answer So the variable y has its value incremented after
\nx = 15, y = 15, z = 0
x = 15, y = 16, z = 15z = (*p)++;
.
Given the following definitions:
\nint arr[] = { 0, 1, 2, 3 };
int *p = arr;
p = p + 1;
p++;
Yes, the two statements p = p + 1;
and p++;
are equivalent in this context. Both statements increment the pointer p to point to the next element in the array arr.
In general, if ptr is a pointer to type T, then ptr + n
will point to the memory location \"ptr + n * sizeof(T)\". This is useful for iterating over arrays or accessing elements in memory sequentially.
Write a function called 'swap' that will accept two pointers to integers and will exchange the contents of those integer locations.
\nShow a call to this subroutine to exchange two variables.
\nHere is the sample code:
\n
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
printf("Before: x = %d, y = %d\\n", x, y);
swap(&x, &y);
printf("After: x = %d, y = %d\\n", x, y);
return 0;
}
Why is it necessary to pass pointers to the integers instead of just passing the integers to the Swap subroutine?
\nIt is necessary to pass pointers to the integers instead of just passing the integers themselves to the swap subroutine because C passes arguments by value. When you pass an integer to a function, a copy of the integer's value is made and passed to the function. Any changes made to the parameter inside the function do not affect the original variable outside the function.
\nBy passing pointers to integers (int *a
and int *b
), you are passing the memory addresses of the integers. This allows the swap function to access and modify the actual integers in memory, rather than working with copies. As a result, the values of the integers are swapped correctly, and the changes are reflected outside the function.
In summary, passing pointers to integers allows the swap function to modify the values of the integers themselves, rather than just copies of the values.
What would happen if you called swap like this:
\nint x = 5;
swap(&x, &x);
If you called swap(&x, &x);
with the same pointer &x
for both arguments, it would effectively try to swap the contents of x with itself. The result would be that x would remain unchanged, as the swap operation would effectively cancel itself out. The swap operation had no net effect on x.
Can you do this: (why or why not?)
\nswap(&123, &456);
What does the following code print:
\nint func() { |
The output is
\n3 |
Explanation:
\narray[2]
which is 9.p++
, p points to array[3]
which is 3. The value 3 is printed.*(--p) = 7;
sets array[3]
to 7.(*p)++;
increments the value at array[3]
(which is now 7) to 8.4 2 9 8 8
.Write a subroutine called clear_it that accepts a pointer to integer and an integer that indicates the size of the space that the pointer points to. clear_it should set all of the elements that the pointer points to to zero.
\nvoid clear_it(int *ptr, int size) {
for (int i = 0; i < size; i++) {
*(ptr + i) = 0;
}
}
Write a subroutine called add_vectors that accepts three pointers to integer and a fourth parameter to indicate the size of the spaces that the pointers point to. add_vectors should add the elements of the first two 'vectors' together and store them in the third 'vector'. e.g. if two arrays of 10 integers, A and B, were to be added together and the result stored in an array C of the same size, the call would look like add_vectors(a, b, c, 10);
and, as a result, c[5] would be the sum of a[5] and b[5]
All four implementations below are equivalent solutions to this problem:
\nvoid add_vectors(int *a, int *b, int *c, int size) {
for (int i = 0; i < size; i++) {
c[i] = a[i] + b[i];
}
}
void add_vectors1(int *a, int *b, int *c, int size) {
int *end = c + size;
while (c < end) {
*c++ = *a++ + *b++;
}
}
void add_vectors2(int *a, int *b, int *c, int size) {
for (int i=0; i<size; i++) {
*c++ = *a++ + *b++;
}
}
void add_vectors3(int *a, int *b, int *c, int size) {
for (int i=0; i<size; i++) {
*(c+i) = *(a+i) + *(b+i);
}
}
Here is a series of general study guides to college-level C programming courses. This is the second part covering dynamic memory allocation, advanced pointer operations, recursion, linked list and tree common functions, etc.
\nGiven the following definitions:
\nint *pi = NULL;
float *pf = NULL;
char *pc = NULL;
char my_string[] = "Hello, World!";
write statements to do the following memory operations:
\nreserve space for 100 integers and assign a pointer to that space to pi
\npi = (int *)malloc(sizeof(int) * 100);
assert(pi != NULL);
reserve space for 5 floats and assign a pointer to that space to pf
\npf = (float *)malloc(sizeof(float) * 5);
assert(pf != NULL);
unreserve the space that pi points to
\nfree(pi);
pi = NULL;
reserve space for enough characters to hold the string in my_string and assign a pointer to that space to pc. Copy my_string into that space.
\npc = (char *)malloc(strlen(my_string) + 1));
assert(pc != NULL);
strcpy(pc, mystring);
free everything that hasn't been unreserved yet.
\nfree(pc);
free(pf);
pc = NULL;
pf = NULL;
What happens if you reserve memory and assign it to a pointer named p and then reserve more memory and assign the new pointer to p? How can you refer to the first memory reservation?
\nIf you reserve then assign then reserve more memory you will have a memory leak. If you want to refer to the first pointer, you can set a new pointer to point to the new one before reserving more memory.
Does it make sense to free() something twice? What's a good way to prevent this from happening?
\nNo, it doesn’t make sense to free something twice, a good way to prevent this is setting the thing you freed to NULL after freeing it.
Suppose p is a pointer to a structure and f is one of its fields. What is a simpler way of saying: x = (*p).f;
.
x = p->f;
Given the following declarations and definitions:
\nstruct s {
\tint x;
\tstruct s *next;
};
struct s *p1 = NULL;
struct s *p2 = NULL;
struct s *p3 = NULL;
struct s *p4 = NULL;
struct s *p5 = NULL;
p5 = malloc(sizeof(struct s));
p5->x = 5;
p5->next = NULL;
p4 = malloc(sizeof(struct s));
p4->x = 4;
p4->next = p5;
p3 = malloc(sizeof(struct s));
p3->x = 3;
p3->next = p4;
p2 = malloc(sizeof(struct s));
p2->x = 2;
p2->next = p3;
p1 = malloc(sizeof(struct s));
p1->x = 1;
p1->next = p2;
printf("%d %d\\n", p1->next->next->next->x, p2->next->x);
It will print \"4 3\".
Write a subroutine called do_allocate
that is passed a pointer to the head pointer to a list of block structures: do_allocate(struct block **)
. If the head pointer is NULL, do_allocate
should allocate a new struct block and make the head pointer point to it. If the head is not NULL, the new struct block should be prepended to the list, and the head pointer set to point to it.
This is a linked list insertion function. New data items should always be inserted into the front of the list. Note the input argument has to be a pointer to pointer to make a change to the original head pointer. A sample solution is shown below
\n
struct block {
int data;
struct block *next;
};
void do_allocate(struct block **head) {
struct block *new_block = malloc(sizeof(struct block));
if (new_block == NULL) {
// Handle memory allocation failure
return;
}
// Initialize the new block
new_block->data = 0;
new_block->next = *head;
// Update the head pointer
*head = new_block;
}
Write a subroutine called my_free that will accept a pointer to a pointer of some arbitrary type and:
\n
void my_free(void **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
Given the following declaration:
\nstruct employee {
char *name;
char *title;
int id;
};
struct employee *create_employee(const char *name, const char *title, int id)
{
struct employee *new_employee = malloc(sizeof(struct employee));
if (new_employee == NULL) {
return NULL;
}
// Allocate memory for the name and copy the string
new_employee->name = malloc(strlen(name) + 1);
if (new_employee->name == NULL) {
free(new_employee);
return NULL;
}
strcpy(new_employee->name, name);
// Allocate memory for the title and copy the string
new_employee->title = malloc(strlen(title) + 1);
if (new_employee->title == NULL) {
free(new_employee->name);
free(new_employee);
return NULL;
}
strcpy(new_employee->title, title);
// Set the ID
new_employee->id = id;
return new_employee;
}
Write a subroutine called fire_employee that accepts a pointer to pointer to struct employee, frees its storage and sets the pointer that points to the storage to NULL.
\nvoid fire_employee(struct employee **emp_ptr) {
if (emp_ptr != NULL && *emp_ptr != NULL) {
\tfree((*emp_ptr)->name);
\tfree((*emp_ptr)->title);
\tfree(*emp_ptr);
\t*emp_ptr = NULL;
}
}
Create a recursive function to compute the factorial function.
\nunsigned long long factorial(unsigned int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
Create a recursive function to compute the Nth element of the Fibonacci sequence: 0 1 1 2 3 5 8 13 21 34 55 ...
\nunsigned int fibonacci(unsigned int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Implement a recursive list search. e.g. each function call should either return the list node that it's looking at because it matches the search item or it should return the value from calling itself on the next item in the list.
\nstruct Node {
int data;
struct Node* next;
};
struct Node* search(struct Node* node, int value) {
if (node == NULL) return NULL;
if (node->data == value) {
return node;
} else {
// Recursive call on the next node
return search(node->next, value);
}
}
|
|
Try the following two programs to appreciate the differences between static and non-static local variables.
\nvoid try() { void try() {
int x = 0; static int x = 0;
if (x == 0) { if (x == 0) {
x = 5; x = 5;
} }
x++; x++;
printf("X = %d\\n", x); printf("X = %d\\n", x);
} }
int main() { int main() {
int i=0; int i=0;
for (i=0; i<10; i++) for (i=0; i<10; i++)
try(); try();
} }
// Output "X = 6" always // Output "X = 6/7/8/..."
What happens if you define a global variable with a static storage class in one module and attempt to refer to that variable in a different module?
\nThe variable will not be accessible in the other module. This is because static variables have internal linkage by default, meaning they are only accessible within the same module.
Can a function be declared with a static storage class? If so, how? If not, why not?
\nYes, you can declare a function with the static storage class, you can use the static keyword. It means that the function has internal linkage, which restricts its scope to the current translation unit (i.e., the source file in which it is defined). This means that the function can only be called from within the same source file, and its name is not visible outside of that file.
Create a global variable in one module and, in another module use an \"extern\" declaration to refer to it.
\nint globalVariable = 42;
extern int globalVariable; // Declare the global variable from module1
int main() {
printf("The value of globalVariable is: %d\\n", globalVariable);
return 0;
}
Under what conditions can you qualify a type as \"const\"?
\nThe const keyword is used to indicate that the value of the object with that type cannot be modified.
What is the difference between the following types?
\nconst char * cp1;
char * const cp2;
const char * const cp3;
const char * cp1;
: This declares cp1 as a pointer to a constant char. It means that the data cp1 points to cannot be modified through cp1, but cp1 itself can be changed to point to a different memory location.
char * const cp2;
: This declares cp2 as a constant pointer to a char. It means that cp2 always points to the same memory location, and this memory location cannot be changed. However, the data at this memory location can be modified through cp2.
const char * const cp3;
: This declares cp3 as a constant pointer to a constant char. It means that both cp3 and the data it points to are constant. cp3 cannot be changed to point to a different memory location, and the data it points to cannot be modified through cp3.
In summary:
\nName all of the first-class types in \"C\".
\nScalar types (e.g., int, float, double, char, void, short, long, etc.)
Give an example of a derived type in \"C\".
\nPointer types (e.g., int *
, char *
, etc.).
\nPointer to function types (e.g., int (*)(int, int)
, a pointer to a function that takes two int
arguments and returns an int)
An example is declaring a struct type, e.g.:
\nstruct person {
\tchar name[20];
\tint age;
\tfloat height;
};
Can you assign a float variable to an int variable?
\nYes, but the value will be truncated.
Can you assign an int variable to a float variable?
\nYes, but the type will be promoted.
Can you assign any first-class type variable to any other first-class type variable?
\nYes, you just have to typecast them to the matching data type.
Can you assign a first-class type variable to any kind of derived type variable?
\nNo, e.g. you cannot assign an int to a structure
|
#define
is a preprocessor directive in C that unconditionally defines a macro.
#ifdef
is a preprocessor directive in the C programming language that tests whether a macro has been defined or not. It allows conditional compilation of code based on whether a particular macro has been defined or not.
#else
is run if the macro is not defined in a #ifdef
#endif
Ends a #ifdef macro
#if
is a preprocessor directive in the C programming language that allows conditional compilation of code based on the value of an expression.
Does the following program cause a compile-time error?
\n
int function() {
\tint x = 0;
\treturn;
\treturn 0;
}#if
directive, it replaces it with B and then replaces B with C. Therefore, the #if
statement is effectively replaced by #if (C == 1)
.
Since C is defined as 1, the condition in the #if
statement evaluates to true, and the code in the first branch of the if statement is executed, which is a return statement without a value.
In this specific case, the program still works because the function return type is int
, and the return statement in the first branch of the if statement might just return some undetermined number.
In general, however, it is good practice to always explicitly return a value from a function that has a return type, as it makes the code more clear and less error-prone.
What are the reasons for using libraries?
\nTo import useful code, promote modular programming, and provide cross-platform compatibility.
What are the differences between static and dynamic (shared) libraries?
\nAspects | \nStatic library | \nDynamic library | \n
---|---|---|
Linking | \nLinked at compile time | \nLinked at run time | \n
Size | \nIncrease the size of the executable (the library code is included in the executable. | \nReduce the size of the executable (the library code is stored separately and referenced at run time) | \n
Memory Usage | \nIncrease memory usage (the entire library code is loaded into memory) | \nReduce memory usage (the code is shared among multiple processes, and only one copy of the library code is loaded into memory) | \n
Ease of Updates | \nRequire recompilation of the entire program | \nAllow for easier updates (can replace the library file without recompiling the program) | \n
Portability | \nMore portable (does not require the presence of the library file at run time) | \nLess portable (requires the library file to be present and correctly configured at run time) | \n
Runtime Dependencies | \nNo (directly included in the executable) | \nYes (must be present in the correct location for the program to run) | \n
What are the trade-offs between the above two?
\nThe trade-offs between static and dynamic libraries involve executable size, memory usage, ease of updates, runtime dependencies, portability, and performance considerations.
How do you create a library?
\nCompile c files into an object file and link them with
gcc (name).o –shared –o library.so
Recently, at a WPA3 technology introduction meeting within the R&D team, the speaker mentioned that the OWE technology for encrypted wireless open networks is based on Diffie-Hellman key exchange, and casually said that Diffie-Hellman key exchange is using technology similar to RSA. This statement is wrong! Although Diffie-Hellman key exchange and RSA encryption algorithms belong to public key cryptography, their working mechanisms and application scenarios are different. As a research and development engineer and technician supporting network security, it is necessary to clearly understand the working mechanism and mathematical principles of the two, as well as the differences and connections between them.
\nA cryptographic system should be secure even if everything about the system, except the key, is public knowledge.
— Auguste Kerckhoffs (Dutch linguist and cryptographer, best known for his “Kerckhoffs's principle” of cryptography)
Diffie-Hellman key exchange (DH for short) is a secure communication protocol that allows two communicating parties to exchange messages over an insecure public channel to create a shared secret without any foreknowledge. This secret can be used to generate keys for subsequent communications between the two parties using symmetric encryption techniques (e.g. AES).
\nThe idea of this kind of public key distribution to achieve shared secrets was first proposed by Ralph Merkle, a doctoral student of Stanford University professor Martin Hellman, and then Professor Hellman's research assistant Whitfield Diffie and Professor Herman jointly invented a practical key exchange protocol. In 1976, Diffie and Hellman were invited to publish their paper \"New Directions in Cryptography\" in IEEE Transactions on Information Theory, which laid the foundation for the public key cryptography system and officially announced the birth of the new Diffie-Herman key exchange technology.
\nThe working principle of Diffie-Hellman key exchange is based on the modular exponentiation operation with the multiplicative group of integers modulo n and its primitive root modulo n in number theory. The following is a simple and specific example to describe:
\nIs it troublesome calculating \\(\\color{#93F}{\\bf62^{39}\\bmod\\;71}\\)? It is actually very easy……
\nRemember that modular arithmetic has the property of preserving primitive operations: \\[(a⋅b)\\bmod\\;m = [(a\\bmod\\;m)⋅(b\\bmod\\;m)]\\bmod\\;m\\] Combining with the principle of Exponentiation by Squaring, and applying the right-to-left binary method to do fast calculation: \\[\\begin{align}\n62^{39}\\bmod\\;71 & = (62^{2^0}⋅62^{2^1}⋅62^{2^2}⋅62^{2^5})\\bmod\\;71\\\\\n& = (62⋅10⋅(62^{2^1}⋅62^{2^1})⋅(62^{2^4}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅(10⋅10)⋅(62^{2^3}⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(29⋅29⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(60⋅60⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(50⋅50))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅15)\\bmod\\;71\\\\\n& = 42\n\\end{align}\\]
\n\nAs if by magic, both Alice and Bob get the same \\(s\\) value of \\(42\\). This is the shared secret of two people! After this, Alice and Bob can use the hash value of \\(s\\) as a symmetric key for encrypted communication, which is unknown to any third party.
\nWhy? Because of the nature of the modular exponentiation of the multiplicative group, \\(g^{ab}\\) and \\(g^{ba}\\) are equal with the modulo \\(p\\):
\n\\[A^b\\bmod\\;p=g^{ab}\\bmod\\;p=g^{ba}\\bmod\\;p=B^a\\bmod\\;p\\]
\nSo calculated \\(s\\) values must be the same. Of course, real applications would use much larger \\(p\\), otherwise the attacker can exhaust all the remainder to try to crack the ciphertext encrypted by the symmetric key.
\nNotice \\((p,g,A,B)\\) is public and \\((a,b,s)\\) is secret. Now suppose an eavesdropper Eve can see all the messages between Alice and Bob, can she deduce \\(s\\)? The answer is that this is only practically possible if the values of \\((p,a,b)\\) are very small. Eve must first invert \\((a,b)\\) from what she knows about \\((p,g,A,B)\\):
\nThis is the famous discrete logarithm problem. It is a recognized computational challenge and no polynomial-time efficient algorithm is currently found to compute the discrete logarithm. So this protocol is considered eavesdropping-safe as long as the appropriate \\((p,a,b)\\) is chosen. RFC 3526 recommends 6 Modular Exponential (MODP) DH groups of large prime numbers for practical applications, the smallest of which has 1536 bits!
\nIt should also be emphasized that Diffie-Hellman key exchange itself does not require authentication of both communicating parties, so it is vulnerable to man-in-the-middle attacks. If an attacker can tamper with the messages sent and received by both sides in the middle of the channel, he can complete Diffie-Hellman key exchange twice by pretending to be an identity. The attacker can then decrypt the entire message. Therefore, usually practical applications need to incorporate authentication mechanisms to prevent such attacks.
\nDiffie-Hellman key exchange technique is a crucial contribution to modern cryptography. In 2015, 39 years after the announcement of this invention, Diffie and Hellman jointly won the ACM Turing Award, known as the \"Nobel Prize of Computing\". The ACM award poster directly stated that they \"invented public key cryptography\".
\nRSA is a public key encryption algorithm. The public key encryption system with the same name as the core technology is widely used in secure data transmission. Today, the comprehensive development of the Internet has provided great convenience to the public in all aspects of society. Whether you are surfing, gaming, entertaining, shopping, instant messaging with friends and family, managing a bank account, investing in financial securities, or simply sending and receiving email, RSA is working behind the scenes to protect your privacy and data security.
\nRSA is actually an acronym for the last names of three people: American cryptographer Ronald Rivest, Israeli cryptographer Adi Shamir, and American computer scientist Leonard Max Adleman. In 1977, Levister, Shamir, and Adleman collaborated at the Massachusetts Institute of Technology (MIT) to invent the RSA encryption algorithm. The algorithm was first published in a public technical report at MIT, and later compiled and published in the February 1978 issue of ACM Communications under the title \"A Method for Obtaining Digital Signatures and Public Key Cryptosystems\".
\nThe basic idea of RSA is that the user creates a key pair consisting of a public key and a private key. The public key is freely distributed and the private key must be kept secret. Anyone can encrypt a message with the public key, and the resulting ciphertext can only be deciphered by the private key holder. On the other hand, any message encrypted with the private key can be decrypted by the public key. Since we assume that the private key can only be held by a specific object, encrypting with the private key is equivalent to generating a digital signature, and decrypting with the public key is equivalent to verifying the signature.
\nThe RSA encryption algorithm consists of a four-step operational process: key generation, key distribution, encryption, and decryption. A simple and concrete example is also given below to illustrate.
\nThe third step above works out \\(d\\) from \\(\\color{#93F}{\\bf(d\\cdot 5)\\;mod\\;52794=1}\\), here's how
\nThe modular multiplicative invers can be solved quickly by applying the Extended Euclidean algorithm. Referring to this Wiki page, with the precondition of coprime, the following equation can be written (\\(gcd\\) is the function for the greatest common divisor function):
\n\\[52794s+5t=\\mathrm{gcd}(5, 52794)=1\\]
\nThe goal is to find the smallest positive integer \\(t\\) that satisfies the above equation. The following table shows the iterative process of the algorithm:
\nIndex \\(i\\) | \nQuotient \\(q_{i-1}\\) | \nRemainder \\(r_i\\) | \n\\(s_i\\) | \n\\(t_i\\) | \n
---|---|---|---|---|
0 | \n\n | \\(52794\\) | \n\\(1\\) | \n\\(0\\) | \n
1 | \n\n | \\(5\\) | \n\\(0\\) | \n\\(1\\) | \n
2 | \n\\(52794 \\div5 = 10558\\) | \n\\(4\\) | \n\\(1 - 10558\\times 0 = 1\\) | \n\\(0 - 10558\\times 1 = -10558\\) | \n
3 | \n\\(5 \\div4 = 1\\) | \n\\(1\\) | \n\\(0-1\\times1 = -1\\) | \n\\(1 - 1\\times (-10558) = \\bf10559\\) | \n
It only takes two iterations to get the remainder \\(1\\) and the algorithm ends. The final \\(t\\) is the \\(5^{-1}\\pmod {52794}\\) we want.
\n\nString together after decoding to get the same information \"CACC 9678\". Why does Alice's decrypted message match exactly the one sent by Bob? The reason lies in the modular exponentiation operation. First of all, because \\(c\\equiv m^e\\pmod N\\), we can get \\(c^d\\equiv (m^e)^d \\equiv m^{ed} \\pmod N\\). Since \\((d⋅e)\\;mod\\;\\lambda(N)=1\\), it is deduced that \\(ed = 1 + h\\lambda(N)\\) (\\(h\\) is a non-negative integer为非负整数). Combine these two
\n\\[\\Rightarrow m^{ed} = m^{(1+h\\lambda(N))} = \\color{fuchsia}{m(m^{\\lambda(N)})^h \\equiv m(1)^h}\\equiv m\\pmod N\\]
\nThe penultimate congruence above (symbol \\(\\equiv\\)) is based on Euler's theorem). This proves the correctness of the decryption formula \\({m\\equiv c^d\\pmod N}\\)! You can also see that the order of \\(e\\) and \\(d\\) is irrelevant for the result of \\(m^{ed}\\pmod N\\), so the message that Alice encrypted with her private key can be decrypted by Bob with Alice's public key. This also proves the feasibility of digital signatures.
\nIn terms of security, if a third party can derive \\(d\\) from Alice's public key \\((N,e)\\), then the algorithm is broken. But the prerequisite for cracking is to first identify \\(p\\) and \\(q\\) from \\(N\\), which is very difficult when \\(N\\) is big. In fact, this is the famous problem of factoring large numbers, another recognized computational challenge. So far, \"the best-known algorithms are faster than exponential order of magnitude times and slower than polynomial order of magnitude times.\" The latest record, published on the RSA Factoring Challenge website, is the February 2020 crack of RSA-250, a large number of 829 bits. This development indicates that the security of 1024-bit \\(N\\)-valued public keys is already in jeopardy. In view of this, National Institute of Standards and Technology (NIST) recommends that RSA keys be at least 2048 bits in length for real-world applications.
\nOn the other hand, although the public key does not need to be transmitted confidentially, it is required to be reliably distributed. Otherwise, Eve could pretend to be Alice and send her own public key to Bob. If Bob believes it, Eve can intercept all messages passed from Bob to Alice and decrypt them with her own private key. Eve will then encrypt this message with Alice's public key and pass it to her. Alice and Bob cannot detect such a man-in-the-middle attack. The solution to this problem is to establish a trusted third-party authority to issue certificates to ensure the reliability of public keys. This is the origin of the Public Key Infrastructure (PKI).
\nThe RSA public key encryption algorithm is the genius creation of three cryptographers and computer scientists. Its invention is a new milestone in public key cryptography and has become the cornerstone of modern secure Internet communication. The outstanding contribution of Levister, Shamir, and Adelman earned them the ACM Turing Award in 2002, a full 13 years before Diffie and Herman!
\nThe following table summarizes the comparison of Diffie-Hellman key exchange and RSA public key encryption algorithm:
\nCryptographic Technology | \nDiffie-Hellman Key Exchange | \nRSA Encryption Algorithm | \n
---|---|---|
Technology Category | \nAsymmetric, Public Key Technology | \nAsymmetric, Public Key Technology | \n
Mathematical Principles | \nInteger modulo \\(n\\) multiplicative groups, primitive roots | \nCarmichael function, modular multiplicative inverse, Euler's theorem | \n
Mathematical Operations | \nModular exponentiation, exponentiation by squaring | \nModular exponentiation, exponentiation by squaring, extended Euclidean algorithms | \n
Public Key | \n\\((p,g,A,B)\\) | \n\\((N,e)\\) | \n
Private Key | \n\\((a,b,s)\\) | \n\\((N,d)\\) | \n
Security | \nDiscrete logarithm problem | \nLarge number prime factorization problem | \n
Typical Applications | \nKey Exchange | \nEncryption/Decryption, Digital Signature | \n
Key Kength | \n\\(\\ge2048\\) bits | \n\\(\\ge2048\\) bits | \n
Authentication | \nRequires external support | \nRequires PKI support for public key distribution | \n
Forward Secrecy | \nSupport | \nNot support | \n
As can be seen, both are asymmetric public key techniques, and both have a public and private key pair. They both use Modular exponentiation and exponentiation by squaring mathematical operations, and the RSA public-key encryption algorithm also requires the application of the extended Euclidean algorithm to solve the modular multiplicative inverse. Despite these similarities, the mathematical principles underlying them are different, and the computational challenges corresponding to their security are different in nature. These characteristics determine that the Diffie-Hellman key exchange can be used for key exchange, but not for encryption/decryption, while the RSA public key encryption algorithm can not only encrypt/decrypt but also support digital signatures. Therefore, the argument that the two use similar technologies cannot be established in general.
\nElGamal encryption based on the evolution of the Diffie-Hellman key exchange can be used to encrypt/decrypt messages, but due to some historical reasons and the great commercial success of the RSA public key encryption algorithm, ElGamal encryption is not popular.
\nIn modern cryptography, key length is defined as the number of bits of a key used by an encryption algorithm. Theoretically, since all algorithms may be cracked by brute force, the key length determines an upper limit on the security of an encryption algorithm. Cryptanalytic study shows that the key strengths of Diffie-Hellman key exchange and RSA public key encryption algorithm are about the same. The computational intensities for breaking discrete logarithms and factoring large numbers are comparable. Therefore, the recommended key length for both cryptographic technologies in practical applications is at least 2048 bits.
\nFor authentication, Diffie-Hellman key exchange requires external support, otherwise it is not resistant to man-in-the-middle attacks. RSA public key encryption algorithm can be used to verify digital signatures, but only if there is a PKI supporting reliable public key distribution. The current system of PKI is quite mature, and there is a special Certificate Authority (CA) that undertakes the responsibility of public key legitimacy checking in the public key system, as well as issues and manages public key digital certificates in X.509 format.
\nOne problem with the RSA public key encryption algorithm in practice is that it does not have Forward Secrecy. Forward Secrecy, sometimes referred to as Perfect Forward Secrecy, is a security property of confidential communication protocols, meaning that the leakage of the long-term used master key does not result in the leakage of past session information. If the system has forward secrecy, it can protect the historical communication records in case of private key leakage. Imagine a situation where, although Eve cannot decrypt the RSA-encrypted messages between Alice and Bob, Eve can archive the entire past message ciphertext. One day in the future, Alice's private key for some reason was leaked, then Eve can decrypt all the message records.
\nThe solution to this problem is Diffie-Hellman key exchange! Remember that the \\((A,B)\\) in the public key of the Diffie-Hellman key exchange is generated by both parties from their respective private keys \\((a,b)\\), so if a random \\((a,b)\\) value is generated at each session, future key leaks will not crack the previous session key. This shows that Diffie-Hellman key exchange supports forward secrecy! If we combine the forward secrecy of Diffie-Hellman key exchange with the digital signature feature of the RSA public key encryption algorithm, we can implement a key exchange with authentication protection. This process can be simplified by the following example.
\nHere the RSA digital signature safeguards the key exchange from man-in-the-middle attacks. Also in the second step above, if a new random number is generated for each session, then even if Alice's or Bob's RSA private keys are leaked one day, it does not threaten the security of previous sessions because the eavesdropper still has to solve the discrete logarithm puzzle. We have also achieved forward secrecy. In fact, this is the working mechanism of the DHE-RSA cipher suite as defined by the ubiquitous Transport Layer Security (TLS) protocol.
\nTransport Layer Security (TLS) and its predecessor Secure Sockets Layer (SSL) is a security protocol that provides security and data integrity for Internet communications. TLS is widely used in applications such as browsers, email, instant messaging, VoIP, and virtual private networks (VPNs), and has become the de facto industry standard for secure Internet communications. Currently, TLS 1.2 is the commonly supported version of the protocol, supporting secure connections over TCP. Datagram Transport Layer Security (DTLS) protocol is also defined for UDP applications. DTLS is much the same as TLS, with some extensions for connectionless UDP transport in terms of reliability and security. DTLS 1.2 matches the functionality of TLS 1.2.
\nThe TLS protocol uses a client-server architectural model. It works by using X.509 authentication and asymmetric encryption algorithms to authenticate the communicating parties, after which keys are exchanged to generate a symmetric encryption session key. This session key is then used to encrypt the data exchanged between the two communicating parties, ensuring the confidentiality and reliability of the information without fear of attack or eavesdropping by third parties. For identification purposes, the TLS 1.2 protocol combines the authentication, key exchange, bulk encryption, and message authentication code algorithms used into the Cipher Suite name. Each Cipher Suite is given a double-byte encoding. The TLS Cipher Suite Registry provides a reference table of all registered Cipher Suite names, sorted by encoding value from small to large.
\nSince the computation intensity of asymmetric encryption algorithms (RSA, etc.) is much higher than that of symmetric encryption algorithms (AES, etc.), practical applications almost always use symmetric encryption algorithms to encrypt messages in batches in terms of performance.
\nTLS 1.2 protocol supports a series of cipher suites that combine the Diffie-Hellman key exchange with the RSA public key encryption algorithm. They all start with TLS_DH_RSA or TLS_DHE_RSA`. The \"E\" in DHE stands for \"Ephemeral\", which means that a random \\((a,b)\\) value is required to be generated for each session. So TLS_DHE_RSA cipher suite can provide forward secrecy, while TLS_DH_RSA cannot, and the former should be preferred in practical applications.
\nHere we take a typical TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite as an example to explain the process of Diffie-Hellman working with RSA to establish a DTLS session. First, explain the composition of the cipher suite.
\nReferring to the packet file dtls-dhe-rsa.pcap captured from the network port, the following handshake protocol message sequence chart can be obtained
\n\nsequenceDiagram\n\nautonumber\nparticipant C as Client\nparticipant S as Server\nNote over C,S: Handshake Protocol\nrect rgb(230, 250, 255)\nC->>S: Client Hello (Cr, Cipher Suites))\nS-->>C: Hello Verify Request (Cookie)\nC->>S: Client Hello (Cr, Cookie, Cipher Suites)\nS-->>C: Server Hello (Sr, Cipher Suite), Certificate (Sn, Se)\nS-->>C: Server Key Exchange (p,g,A,Ss)\nS-->>C: Certificate Request, Server Hello Done\nC->>S: Certificate (Cn, Ce)\nC->>S: Client Key Exchange (B)\nC->>S: Certificate Verify (Cs)\nend\nNote over C,S: Establish Secure Channel\nrect rgb(239, 252, 202)\nC->>S: Change Cipher Spec, Encrypted Handshake Message\nS-->>C: Change Cipher Spec, Encrypted Handshake Message\nC->>S: Application Data\nS-->>C: Application Data\nend\n \n\n
Below is the analysis with regard to the data package numbers in the message sequence chart:
\nHello verification is specific to DTLS to prevent denial of service attacks. The protocol stipulates that the server will not continue to serve the client until it receives a hello message containing the copied cookie.
\nNote: If DH-RSA cipher suite is used, the server-side DH public key parameters \\((p,g,A)\\) are unchanged and will be included directly in its certificate message. At this time, the server will not issue a Key Exchange message \\(\\require{enclose}\\enclose{circle}{5}\\). For DHE-RSA, the \\(A\\) value is different for each session.
\nThis is the complete process of establishing a secure message channel using the TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite, where DHE implements a key exchange with forward secrecy protection and RSA digital signature provides authentication for DHE, creating a solution for secure communication. With a clear understanding of this, we will better grasp the working mechanism of Diffie-Hellman and RSA, effectively apply them in practice and avoid unnecessary mistakes.
\n","categories":["Study Notes"],"tags":["Cryptography","Network Security"]},{"title":"Understand Endianness","url":"/en/2021/12/24/Endianness/","content":"The problem of Endianness is essentially a question about how computers store large numbers.
\nI do not fear computers. I fear lack of them.
— Isaac Asimov (American writer and professor of biochemistry, best known for his hard science fiction)
We know that one basic memory unit can hold one byte, and each memory unit has its address. For an integer larger than decimal 255 (0xff in hexadecimal), more than one memory unit is required. For example, 4660 is 0x1234 in hexadecimal and requires two bytes. Different computer systems use different methods to store these two bytes. In our common PC, the least-significant byte 0x34 is stored in the low address memory unit and the most-significant byte 0x12 is stored in the high address memory unit. While in Sun workstations, the opposite is true, with 0x34 in the high address memory unit and 0x12 in the low address memory unit. The former is called Little Endian
and the latter is Big Endian
.
How can I remember these two data storing modes? It is quite simple. First, remember that the addresses of the memory units we are talking about are always arranged from low to high. For a multi-byte number, if the first byte in the low address you see is the least-significant byte, the system is Little Endian
, where Little matches low
. On the contrary is Big Endian
, where Big corresponds to \"high\".
To deepen our understanding of Endianness, let's look at the following example of a C program:
\nchar a = 1; \t \t \t |
On Intel 80x86 based systems, the memory content corresponding to variables a, b, c, and d are shown in the following table:
\nAddress Offset | \nMemory Content | \n
---|---|
0x0000 | \n01 02 FF 00 | \n
0x0004 | \n11 22 33 44 | \n
We can immediately tell that this system is Little Endian
. For a 16-bit integer short c
, we see the least-significant byte 0xff first, and the next one is 0x00. Similarly for a 32-bit integer long d
, the least-significant byte 0x11 is stored at the lowest address 0x0004. If this is in a Big Endian
computer, memory content would be 01 02 00 FF 44 33 22 11.
At the run time all computer processors must choose between these two Endians. The following is a shortlist of processor types with supported Endian modes:
\nBig Endian
: Sun SPARC, Motorola 68000, Java Virtual MachineBig Endian
mode: MIPS with IRIX, PA-RISC, most Power and PowerPC systemsLittle Endian
mode: ARM, MIPS with Ultrix, most DEC Alpha, IA-64 with LinuxLittle Endian
: Intel x86, AMD64, DEC VAXHow to detect the Endianess of local system in the program? The following function can be called for a quick check. If the return value is 1, it is Little Endian
, else Big Endian
:
int test_endian() { |
Endianness is also important for computer communications. Imagine that when a Little Endian
system communicates with a Big Endian
system, the receiver and sender will interpret the data completely differently if not handled properly. For example, for the variable d in the C program segment above, the Little Endian
sender sends 11 22 33 44 four bytes, which the Big Endian
receiver converts to the value 0x11223344. This is very different from the original value. To solve this problem, the TCP/IP protocol specifies a special \"network byte order\" (referred to as \"network order\"), which means that regardless of the Endian supported by the computer system, the most-significant byte is always sent first while transmitting data. From the definition, we can see that the network order corresponds to the Big Endian
.
To avoid communication problems caused by Endianness and to facilitate software developers to write portable programs, some C preprocessing macros are defined for conversion between network bytes and local byte order. htons()
and htonl()
are used to convert local byte order to network byte order, the former works with 16-bit unsigned numbers and the latter for 32-bit unsigned numbers. ntohs()
and ntohl()
implement the conversion in the opposite direction. The prototype definitions of these four macros can be found as follows (available in the netinet/in.h
file on Linux systems).
In the history of mathematics, Pierre de Fermat was a special figure. His formal occupation was as a lawyer, but he was exceptionally fond of mathematics. Although an amateur, Fermat’s achievements in mathematics were no less than those of professional mathematicians of the same era. He contributed to modern calculus, analytic geometry, probability, and number theory. Especially in the field of number theory, Fermat was most interested and achieved the most outstanding results.
\nLogic is the foundation of the certainty of all the knowledge we acquire.
— Leonhard Euler (Swiss mathematician, physicist, astronomer, geographer, logician, and engineer, one of the greatest mathematicians in history)
As the \"king of amateur mathematicians\", Fermat proposed some famous conjectures in number theory but did not give strong proof. The most famous is Fermat's Last Theorem1. Although Fermat claimed he had found an ingenious proof, there was not enough space on the margin to write it down. But in fact, after more than 350 years of unremitting efforts by mathematicians, it was not until 1995 that British mathematician Andrew John Wiles and his student Richard Taylor published a widely recognized proof.
\nIn contrast, there is also a little theorem of Fermat. In October 1640, Fermat first wrote down words equivalent to the following in a letter to a friend:
\n\n\nIf \\(p\\) is a prime and \\(a\\) is any integer not divisible by \\(p\\), then \\(a^{p-1}-1\\) is divisible by \\(p\\).
\n
Similarly, Fermat did not give proof in the letter. Nearly a hundred years later, the complete proof was first published by the great mathematician Euler in 1736. Later, people found in the unpublished manuscripts of another great mathematician Leibniz that he had obtained almost the same proof before 1683.
\nFermat's little theorem is one of the fundamental results of elementary number theory. This theorem can be used to generate primality testing rules and corresponding verification algorithms. In the late 1970s, public key cryptography emerged, and Fermat's little theorem helped prove the correctness of RSA. Afterward, researchers combined it with the Chinese remainder theorem and also discovered an optimized method for RSA decryption and signing. The following further introduces these applications.
\nThe complete statement of Fermat's little theorem is: If \\(\\pmb{p}\\) is a prime number, then for any integer \\(\\pmb{a}\\), the number \\(\\pmb{a^p−a}\\) is an integer multiple of \\(\\pmb{p}\\). In the notation of modular arithmetic, this is expressed as \\(\\pmb{a^p\\equiv a\\pmod p}\\). If \\(\\pmb{a}\\) is not divisible by \\(\\pmb{p}\\), then \\(\\pmb{a^{p-1}\\equiv 1\\pmod p}\\).
\nFrom \\(a^{p-1}\\equiv 1\\pmod p\\) it can be deduced that \\(\\pmb{a^{p-2}\\equiv a^{-1}\\pmod p}\\). This new congruence just gives a way to find the multiplicative inverse of \\(a\\) modulo \\(p\\). This is a direct corollary of Fermat's little theorem.
\nAnother important corollary is: If \\(\\pmb{a}\\) is not a multiple of \\(\\pmb{p}\\) and \\(\\pmb{n=m\\bmod {(p-1)}}\\), then \\(\\pmb{a^n\\equiv a^m\\pmod p}\\). This inference does not seem very intuitive, but the proof is simple:
\nThere are many ways to prove Fermat's little theorem. Among them, mathematical induction based on the binomial theorem is the most intuitive one. First, for \\(a=1\\), it is obvious that \\(1^p \\equiv 1\\pmod{p}\\) holds. Now assume that for an integer \\(a\\), \\(a^p \\equiv a \\pmod{p}\\) is true. As long as it is proved under this condition that \\((a+1)^p\\equiv a+1\\pmod{p}\\), the proposition holds.
\nAccording to the binomial theorem, \\[(a+1)^p = a^p + {p \\choose 1} a^{p-1} + {p \\choose 2} a^{p-2} + \\cdots + {p \\choose p-1} a + 1\\] Here the binomial coefficient is defined as \\({p \\choose k}= \\frac{p!}{k! (p-k)!}\\). Note that because \\(p\\) is a prime number, for \\(1≤k≤p-1\\), each binomial coefficient \\({p \\choose k}\\)is a multiple of \\(p\\).
\nThen taking \\(\\bmod p\\), all the intermediate terms disappear, leaving only \\(a^p+1\\) \\[(a+1)^p \\equiv a^p + 1 \\pmod{p}\\]Referring to the previous assumption \\(a^p ≡ a \\pmod p\\), it infers that \\((a+1)^p \\equiv a+1 \\pmod{p}\\), the proof is complete.
\nFermat's little theorem provides concise solutions to some seemingly complicated computational problems. First look at a simple example: If today is Sunday, what day will it be in \\(2^{100}\\) days? There are 7 days in a week. According to Fermat's little theorem, we have \\(2^{7−1}≡1\\bmod 7\\), from which we can get \\[2^{100}=2^{16×6+4} ≡ 1^{16}×2^4≡16≡2\\pmod 7\\]So the answer is Tuesday. This actually repeats the proof process of the second corollary above with specific numbers. Applying this corollary can greatly speed up modular exponentiation. For example, to calculate \\(49^{901}\\bmod 151\\), since \\(901\\bmod(151-1)=1\\), it can be deduced immediately that \\[49^{901}\\equiv 49^1\\equiv 49\\pmod {151}\\]
\nNow look at a question that seems a little more difficult: Given the equation \\(133^5+110^5+84^5+27^5=n^{5}\\), find the value of \\(n\\).
\nAt first glance, there seems to be no clue, so start with basic parity checking. The left side of the equation has two odd terms and two even terms, so the total is even, which also determines that \\(n\\) must be even. Looking at the exponent 5 which is a prime number, and thinking of Fermat's little theorem, we get \\(n^5≡n\\pmod 5\\), therefore \\[133^5+110^5+84^5+27^5≡n\\pmod 5\\] \\[3+0+4+2≡4≡n\\pmod 5\\] Continuing to take modulo 3, according to the corollary of Fermat's little theorem again, we have \\(n^5≡n^{5\\mod(3-1)}≡n\\pmod 3\\). So \\[133^5+110^5+84^5+27^5≡n\\pmod 3\\] \\[1+2+0+0≡0≡n\\pmod 3\\]
\nOkay, now summarize:
\nThese lead to \\(n = 144\\) or \\(n\\geq 174\\). Obviously, 174 is too big. It can be concluded that n can only be 144.
\nThis question actually appeared in the 1989 American Invitational Mathematics Examination (AIME), which is a math competition for high school students. Interestingly, the solution to the question happens to disprove Euler's conjecture.
\nMany encryption algorithm applications require \"random\" large prime numbers. The common method to generate large primes is to randomly generate an integer and then test for primality. Since Fermat’s little theorem holds on the premise that p is a prime number, this provides a prime test method called the Fermat primality test. The test algorithm is
\n\n\nInput: \\(n\\) - the number to be tested, \\(n>3\\); \\(k\\) - the number of iterations
\n
\nOutput: \\(n\\) is composite, otherwise may be prime
\nRepeat k times:
\n\\(\\quad\\quad\\)Randomly select an integer \\(a\\) between \\([2, n-2]\\)
\n\\(\\quad\\quad\\)If \\(a^{n-1}\\not \\equiv 1{\\pmod n}\\), return \\(n\\) is composite
\nReturn \\(n\\) may be prime
It can be seen that Fermat’s primality test is non-deterministic. It uses a probabilistic algorithm to determine whether a number is composite or probably prime. When the output is composite, the result is definitely correct; but those numbers tested to be probably prime may actually be composite, such numbers are called Fermat pseudoprimes. The smallest Fermat pseudoprime is 341, with \\(2^{340}\\equiv1\\pmod {341}\\) but \\(341=11×31\\). So in fact, Fermat's little theorem provides a necessary but insufficient condition for determining prime numbers. It can only be said that the more iterations performed, the higher the probability that the tested number is prime.
\nThere is also a class of Fermat pseudoprimes \\(n\\) which are composite numbers themselves, but for any integer \\(x\\) that is coprime with \\(n\\), it holds \\(x^{n-1}\\equiv 1\\pmod n\\). In number theory, they are called Carmichael numbers. The smallest Carmichael number is 561, equal to \\(3×11×17\\). Carmichael numbers can fool Fermat’s primality test, making the test unreliable. Fortunately, such numbers are very rare. Statistics show that among the first \\(10^{12}\\) natural numbers there are only 8241 Carmichael numbers.
\nThe PGP encryption communication program uses Fermat’s primality test in its algorithm. In network communication applications requiring large primes, Fermat’s primality test method is often used for pretesting, followed by calling the more efficient Miller-Rabin primality test to ensure high accuracy.
\nFermat's little theorem can also be used to prove the correctness of the RSA algorithm, that is, the decryption formula can completely restore the plaintext \\(m\\) from the ciphertext \\(c\\) without errors: \\[c^d=(m^{e})^{d}\\equiv m\\pmod {pq}\\]
\nHere \\(p\\) and \\(q\\) are different prime numbers, \\(e\\) and \\(d\\) are positive integers that satisfy \\(ed≡1\\pmod {λ(pq)}\\), where \\(λ(pq)=\\mathrm{lcm}(p−1,q−1)\\). \\(\\mathrm{lcm}\\) is the least common multiple function.
\nBefore starting the proof, first introduce a corollary of the Chinese remainder theorem: If integers \\(\\pmb{n_1,n_2,...,n_k}\\) are pairwise coprime and \\(\\pmb{n=n_{1}n_{2}...n_{k}}\\), then for any integer \\(\\pmb x\\) and \\(\\pmb y\\), \\(\\pmb{x≡y\\pmod n}\\) holds if and only if \\(\\pmb{x≡y\\pmod{n_i}}\\) for each \\(\\pmb{i=1,2,...k}\\). This corollary is easy to prove, details are left as an exercise2. According to this corollary, if \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) are both true, then \\(m^{ed}≡m\\pmod{pq}\\) must also hold.
\nNow look at the first step of the proof. From the relationship between \\(e\\) and \\(d\\), it follows \\(ed-1\\) can be divided by both \\(p-1\\) and \\(q-1\\), that is, there exist non-negative integers \\(h\\) and \\(k\\) satisfying: \\[ed-1=h(p-1)=k(q-1)\\]
\nThe second step is to prove \\(m^{ed}≡m\\pmod p\\). Consider two cases:
\nThe third step has the goal of proving \\(m^{ed}≡m\\pmod q\\). The deduction process is similar to the previous step, and it can also be deduced that m^ed ≡ m (mod q):
\nSince both \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) have been proved, \\(m^{ed}≡m\\pmod{pq}\\) holds, Q.E.D.
\nCombining Fermat’s little theorem and the Chinese remainder theorem can not only verify the correctness of the RSA but also deduce an optimized decryption method.
\nIn the RSA encryption algorithm, the modulus \\(N\\) is the product of two prime numbers \\(p\\) and \\(q\\). Therefore, for any number \\(m\\) less than \\(N\\), letting \\(m_1=m\\bmod p\\) and \\(m_2=m\\bmod q\\), \\(m\\) is uniquely determined by \\((m_1,m_2)\\). According to the Chinese remainder theorem, we can use the general solution formula to deduce \\(m\\) from \\((m_1,m_2)\\). Since \\(p\\) and \\(q\\) each have only half the number of bits as \\(N\\), modular arithmetic will be more efficient than directly computing \\(c^d\\equiv m\\pmod N\\). And in the process of calculating \\((m_1,m_2)\\), applying the corollary of Fermat's little theorem yields: \\[\\begin{align}\nm_1&=m\\bmod p=(c^d\\bmod N)\\bmod p\\\\\n&=c^d\\bmod p=c^{d\\mod(p-1)}\\bmod p\\tag{1}\\label{eq1}\\\\\nm_2&=m\\bmod q=(c^d\\bmod N)\\bmod q\\\\\n&=c^d\\bmod q=c^{d\\mod(q-1)}\\bmod q\\tag{2}\\label{eq2}\\\\\n\\end{align}\\]
\nObviously, in above \\((1)\\) and \\((2)\\) the exponent \\(d\\) is reduced to \\(d_P=d\\bmod (p-1)\\) and \\(d_Q=d\\bmod (q-1)\\) respectively, which further speeds up the calculation. Finally, the step of calculating \\(m\\) is further optimized using the Garner algorithm3: \\[\\begin{align}\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\\\\\nh&=q_{\\text{inv}}(m_{1}-m_{2})\\pmod {p}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{3}\\label{eq3}\n\\end{align}\\] Note that given \\((p,q,d)\\), the values of \\((d_P,d_Q,q_\\text{inv})\\) are determined. So they can be precomputed and stored. For decryption, only \\((m_1,m_2,h)\\) are to be calculated and substituted into the above (3).
\nThis is actually the decryption algorithm specified in the RSA cryptography standard RFC 8017 (PKCS #1 v2.2). The ASN.1 formatted key data sequence described by this specification corresponds exactly to the above description (\\(d_P\\) - exponent1,\\(d_Q\\) - exponent2,\\(q_{\\text{inv}}\\) - coefficient):
\nRSAPrivateKey ::= SEQUENCE { |
The widely used open-source library OpenSSL implements this efficient and practical decryption algorithm. As shown below, the key data generated using the OpenSSL command line tool is consistent with the PKCS #1 standard:
\n# Generate 512-bit RSA keys saved in PEM format file. |
Also known as \"Fermat's conjecture\",its gist is that, when \\(n > 2\\), the equation \\(x^{n}+y^{n}=z^{n}\\) has no positive integer solutions \\((x, y, z)\\). After it was finally proven correct in 1995, it became known as \"Fermat's last theorem.\"↩︎
Hint: If two integers are congruent modulo \\(n\\), then \\(n\\) is a divisor of their difference.↩︎
Garner, H., \"The Residue Number System\", IRE Transactions on Electronic Computers, Volume EC-8, Issue 2, pp.140-147, DOI 10.1109/TEC.1959.5219515, June 1959↩︎
About the IP packet header checksum algorithm, simply put, it is 16-bit ones' complement of the ones' complement sum of all 16-bit words in the header. However, not many sources show exactly how this is done. The same checksum algorithm is used by TCP segment and UDP datagram, but the data involved in the checksum computing is different from that in the IP header. In addition, the checksum operation of the IPv6 packet is different from that of IPv4. Therefore, it is necessary to make a comprehensive analysis of the checksum algorithm of IP packets.
\nNothing in life is to be feared, it is only to be understood.
— Marie Curie (Polish and naturalized-French physicist and chemist, twice Nobel Prize winner)
IPv4 packet header format can be seen below
\n0 1 2 3 |
Here the 16-bit Header Checksum field is used for error-checking of the IPv4 header. While computing the IPv4 header checksum, the sender first clears the checksum field to zero, then calculates the sum of each 16-bit value within the header. The sum is saved in a 32-bit value. If the total number of bytes is odd, the last byte is added separately.
\nAfter all additions, the higher 16 bits saving the carry is added to the lower 16 bits. Repeat this till all higher 16 bits are zeros. Finally, the sender takes the ones' complement of the lower 16 bits of the result and writes it to the IP header checksum field.
\nThe following demonstrates the entire calculation process using actual captured IPv4 packets.
\n0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00 |
At the beginning of the above 16-bit hex dump is the Ethernet frame header. The IP packet header starts from offset 0x000e, with the first byte 0x45 and the last byte 0xe9. Based on the previous description of the algorithm, we can make the following calculations:
\n(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 + |
Notice at step (1) we replace the checksum field with 0x0000. As can be seen, the calculated header checksum 0x598f is the same as the value in the captured packet. This calculating process is only used for the sender to generate the initial checksum. In practice, for the intermediate forwarding router and the final receiver, they can just sum up all header fields of the received IP packet by the same algorithm. If the result is 0xffff, the checksum verification passes.
\nHow to program IPv4 header checksum computing? RFC 1071 (Computing the Internet Checksum) shows a reference \"C\" language implementation:
\n{ |
In a real network connection, the source device can call the above code to generate the initial IPv4 header checksum. This checksum is then updated at each step of the routing hop because the router must decrement the Time To Live (TTL) field. RFC 1141 (Incremental Updating of the Internet Checksum) gives a reference implementation of fast checksum update:
\nunsigned long sum; |
For TCP segment and UDP datagram, both have 16-bit header checksum fields used for error-checking by the destination host. The checksum computing algorithm is the same as the IP header, except for the difference of covered data. Here the checksum is calculated over the whole TCP/UDP header and the payload, plus a pseudo-header that mimics the IPv4 header as shown below:
\n0 7 8 15 16 23 24 31 |
It consists of the source and destination IP addresses, the protocol number (TCP:6/UDP:17), and the total length of the TCP/UDP header and payload (in bytes). The purpose of including the pseudo-header in the checksum computing is to confirm the packet reaches the expected destination and avoid IP spoofing attacks. Besides, for IPv4 UDP header checksum is optional, it carries all-zeros if unused.
\nIPv6 is IP protocol version 6, and its main design goal was to resolve the problem of IPv4 address exhaustion. Of course, it provides many benefits in other aspects. Although IPv6 usage is growing slowly, the trend is unstoppable. The latest IPv6 standard is published in RFC 8200(Internet Protocol, Version 6 (IPv6) Specification).
\nIPv6 packet header format can be seen below
\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
Notice that the IPv6 header does not include a checksum field, a significant difference from IPv4. The absence of a checksum in the IPv6 header furthers the end-to-end principle of Internet design, to simplify router processing and speed up the packet transmission. Protection for data integrity can be accomplished by error detection at the link layer or the higher-layer protocols between endpoints (such as TCP/UDP on the transport layer). This is why IPv6 forces the UDP layer to set the header checksum.
\nFor IPv6 TCP segment and UDP datagram header checksum computing, the pseudo-header that mimics the IPv6 header is shown below
\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
In actual IPv6 network applications, UDP-Lite (Lightweight UDP) can be used to balance error detection and transmission efficiency. UDP-Lite has its own protocol number 136, and its standard is described in RFC 3828 (The Lightweight User Datagram Protocol (UDP-Lite)).
\nReferring to the following header format, UDP-Lite uses the same set of port number values assigned by the IANA for use by UDP. But it redefines the Length field in the UDP header to a Checksum Coverage, which allows the application layer to control the length of checksummed data. This is useful for the application that can be tolerant of the potentially lossy transmission of the uncovered portion of the data.
\n0 15 16 31 |
UDP-Lite protocol defines the values of \"Checksum Coverage\" (in bytes) as shown in the following table:
\nChecksum Coverage | \nCoverage Area | \nDescription | \n
---|---|---|
0 | \nentire UDP-Lites datagram | \nCalculation covers IP pseudo-header | \n
1-7 | \n(invalid) | \nThe receiver has to drop the datagram | \n
8 | \nUDP-Lites header | \nCalculation covers IP pseudo-header | \n
> 8 | \nUDP-Lites header + portion of payload data | \nCalculation covers IP pseudo-header | \n
> IP datagram length | \n(invalid) | \nThe receiver has to drop the datagram | \n
For multimedia applications running VoIP or streaming video data transmission protocols, it'd better receive data with some degree of corruption than not receiving any data at all. Another example is the CAPWAP protocol used to connect Cisco wireless controller and access points. It specifies UDP-Lite as the default transport protocol for the CAPWAP Data channel, while the connection is established over the IPv6 network.
\nAt last, share a C program snippet to present how to initialize a Berkeley socket to establish an IPv6 UDP-Lite connection:
\n
|
Here IPPROTO_UDPLITE
is protocol number 136, which is used together with AF_INET6
address family parameter in socket()
function call for IPv6 socket creation. The UDPLITE_SEND_CSCOV
(10) and UDPLITE_RECV_CSCOV
(11) are the control parameters of socket options configuration function setsockopt()
, used for setting the Checksum Coverage value in the sender and the receiver respectively. Remember that both the sender and the receiver must set the same value, otherwise, the receiver will not be able to verify the checksum properly.
IPv6 supports multiple addresses, making address assignments more flexible and convenient. Unlike IPv4, which relied solely on the DHCP protocol for address assignment, IPv6 incorporates a native Stateless Address AutoConfiguration SLAAC) protocol. SLAAC can either work alone to provide IPv6 addresses to hosts, or it can work with DHCPv6 to generate new assignment schemes. Here is a comprehensive analysis of the dynamic address allocation mechanism for IPv6.
\nWho the hell knew how much address space we needed?
— Vint Cerf (American Internet pioneer and one of \"the fathers of the Internet\")
The most significant difference between IPv6 and IPv4 is its large address space. IPv4 has 32 bits (4 bytes) and allows for approximately 4.29 (232) billion addresses. IPv6, on the other hand, defines 128 bits (16 bytes) and supports approximately 340 x 1036 addresses. This is a pretty impressive number, and there will be no address depletion for the foreseeable future. A typical IPv6 address can be divided into two parts. As shown in the figure below, the first 64 bits are used to represent the network, and the next 64 bits are used as the interface identifier.
The interface identifier can be generated in several ways:
\nIETF recommends a canonical textual representation format for ease of writing. It includes leading zeros suppression and compression of consecutive all-zero fields. With the network prefix length at the end, the above address can be shortened to 2001:db8:130f::7000:0:140b/64.
\nRFC 4291 defines three types of addresses:
\nNote that there are no broadcast addresses in IPv6, their function being superseded by multicast addresses. Anycast addresses are syntactically indistinguishable from unicast addresses and have very limited applications. A typical application for anycast is to set up a DNS root server to allow hosts to look up domain names in close proximity. For unicast and multicast addresses, they can be identified by different network prefixes:
\nAddress Type | \nBinary Form | \nHexadecimal Form | \nApplication | \n
---|---|---|---|
Link-local address (unicast) | \n1111 1110 10 | \nfe80::/10 | \nUse on a single link, non-routable | \n
Unique local address (unicast) | \n1111 1101 | \nfd00::/8 | \nAnalogous to IPv4 private network addressing | \n
Global unicast address | \n001 | \n2000::/3 | \nInternet communications | \n
Multicast address | \n1111 1111 | \nff00::/8 | \nGroup communications, video streaming | \n
Each interface of a host must have a link-local address. Additionally, it can be manually or dynamically autoconfigured to obtain a unique local address and a global unicast address. Thus, IPv6 interfaces naturally have multiple unicast addresses. Unique local addresses are managed by the local network administrator, while the global unicast addresses are allocated by the IANA-designated regional registry. Referring to the following diagram, all current global unicast addresses are assigned from the 2000::/3 address block, with the first 48 bits of the address identifying the service provider's global routing network and the next 16 bits identifying the enterprise or campus internal subnet: Because an IPv6 multicast address can only be used as a destination address, its bit definition is different from that of unicast. Referring to RFC 4291, a multicast address containing 4 bits of the feature flags, 4 bits of the group scope, and the last 112 bits of the group identifier:
Furthermore the same protocol specifies a few pre-defined IPv6 multicast addresses, the most important of which are
IPv6 dynamic address assignment depends on Neighbor Discovery Protocol (NDP). NDP acts at the data link layer and is responsible for discovering other nodes and corresponding IPv6 addresses on the link and determining available routes and maintaining information reachability to other active nodes. It provides the IPv6 network with the equivalent of the Address Resolution Protocol (ARP) and ICMP router discovery and redirection protocols in IPv4 networks. However, NDP adds many improvements and new features. NDP defines five ICMPv6 message types:
\nThe first two message types here, RS and RA, are the keys to implementing dynamic IPv6 address assignment. The host sends an RS message to the multicast address ff02::2 of all routers in the local network segment to request routing information. When the router receives the RS from the network node, it sends an immediate RA in response. The message format of the RA is as follows
\n0 1 2 3 |
It defines two special bits, M and O, with the following meaning:
\nThe RA message ends with the Options section, which originally had three possible options: Source Link-Layer Address, MTU, and Prefix Information. Later, RFC 8106 (which replaced RFC 6106) added the Recursive DNS Server (RDNSS) and DNS Search List (DNSSL) options. The Prefix Information option directly provide hosts with on-link prefixes and prefixes for Address Autoconfiguration, and it has the following format
\n0 1 2 3 |
Here the Prefix Length and the Prefix jointly determine the network prefix of the IPv6 address. In addition, the Prefix Information option also defines two special bits, L and A:
\nSimilar to the IPv4 subnet mask feature, the purpose of the \"on-link\" determination is to allow the host to determine which networks an interface can access. By default, the host only considers the network where the link-local address is located as \"on-link\". If the \"on-link\" status of a destination address cannot be determined, the host forwards the IPv6 datagram to the default gateway (or default router) by default. When the host receives an RA message, if the \"on-link\" flag for a prefix information option is set to 1 and the Valid Lifetime is also a non-zero value, the host creates a new prefix network entry for it in the prefix list. All unexpired prefix network entries are \"on-link\".
\nAfter understanding the NDP protocol and the information conveyed by the RA messages, let's see how they guide the network nodes to achieve dynamic address assignment.
\nRouters in the network periodically send RA messages to the multicast addresses (ff02::1) of all nodes in the local subnet. However, to avoid latency, the host sends one or more RS messages to all routers in the local subnet as soon as it has finished booting. The protocol requires the routers to respond to the RA messages within 0.5 seconds. Then, based on the values of the M/O/A bits in the received RA messages, the host decides how to dynamically configure the unique local and global unicast addresses of the interface and how to obtain other configuration information. With certain combinations of bit fetch values, the host needs to run DHCPv6 client software to connect to the server to obtain address assignment and/or other configuration information. The entire process is shown in the following message sequence diagram.
\n\nsequenceDiagram\n\nparticipant R as Router\nparticipant H as Host\nparticipant S as DHCPv6 Server\nNote over R,H: Router Request\nrect rgb(239, 252, 202)\nH->>R: Router Solicitation\nR-->>H: Router Advertisement\nend\nNote over H,S: Address Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Solicit\nS-->>H: DHCPv6 Advertise\nH->>S: DHCPv6 Request\nS-->>H: DHCPv6 Reply\nend\nNote over H,S: Other Information Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Information-request\nS-->>H: DHCPv6 Reply\nend\n\n\n
Note: Unlike the IPv4 DHCP protocol, DHCPv6 clients use UDP port 546 and servers use UDP port 547.
\nNext explain in detail three dynamic allocation schemes determined by the combination of the M/O/A-bit values:
\nSLAAC is the simplest automatic IPv6 address assignment scheme and does not require any server. It works by sending an RS message request after the host starts up and the router sends back RA messages to all nodes in the local network segment. If the RA message contains the following configuration
\nThen the host receives this RA message and performs the following operations to implement SLAAC:
\nThis way, the host gets one or more IPv6 unique local addresses or global unicast addresses, plus the default gateway and domain name service information to complete various Internet connections.
\nThe following is an example of the SLAAC configuration on a Cisco Catalyst 9300 Multilayer Access Switch:
\nipv6 unicast-routing |
The Layer 3 interface of the Cisco Multilayer Switch provides routing functionality. As you can see, when IPv6 is activated on the Layer 3 interface in VLAN 10, its default address auto-assignment scheme is SLAAC. the control bits of RA messages from this interface are all set according to the SLAAC scheme, and the network prefixes for each IPv6 address it configures are automatically added to the RA prefix information options list. Of course, the network administrator can also exclude certain network prefixes with a separate interface configuration command. The last two lines of the example configuration command specify RDNSS and DNSSL, which are also added to the RA message options.
\nIf a host connects to a port in VLAN 10, it immediately gets a global unicast address with the network prefix of 2001:ABCD:1000::/64, and its default gateway address is set to 2001:ABCD:1000::1. Open a browser and enter a URL, and it will send a message to the specified domain name server 2001:4860:4860::8888 (Google's public name server address) to obtain the IPv6 address of the destination URL to establish a connection.
\nSLAAC automatic address assignment is fast and easy, providing a plug-and-play IPv6 deployment solution for small and medium-sized network deployments. However, if a network node needs access to additional configuration information, such as NTP/SNTP server, TFTP server, and SIP server addresses, or if its functionality relies on certain Vendor-specific Information Options, it must choose SLAAC + stateless DHCPv6 scheme.
\nThis scenario still uses SLAAC automatic address assignment, but the router instructs the host to connect to a DHCPv6 server for additional configuration information. At this point, the RA message sent back by the router has
\nAfter receiving this RA message, the host performs the following actions:
\nAs you can see, SLAAC + stateless DHCPv6 is not different from SLAAC in terms of address assignment. DHCPv6 only provides additional configuration information and does not assign IPv6 addresses. So the DHCPv6 server does not track the address assignment status of network nodes, which is what \"stateless\" means.
\nThe corresponding configuration commands on the Catalyst 9300 switch are as follows.
\nipv6 unicast-routing |
The difference with the SLAAC example is that the VLAN 10 interface configuration command ipv6 nd other-config-flag
explicitly specifies to set the O-bit of the RA message. Its next command, ipv6 dhcp server vlan-10-clients
, activates the DHCPv6 server response feature of the interface, corresponding to the server's pool name of vlan-10-clients
. The DHCPv6 server is configured above the interface configuration, starting at ipv6 dhcp pool vlan-10-clients
, and contains the DNS server address, DNS domain name, and SNTP server address.
If you are using a separate DHCPv6 server located on a network segment, you can remove the ipv6 dhcp server
command and enable the ipv6 dhcp relay destination
command on the next line of the example to specify the address to forward DHCPv6 requests to the external server.
Many large enterprises use DHCP to manage the IPv4 addresses of their devices, so deploying DHCPv6 to centrally assign and manage IPv6 addresses is a natural preference. This is where Stateful DHCPv6 comes into play. This scenario also requires RA messages sent by the router but does not rely solely on network prefixes for automatic address assignment. The control bits of the RA messages are configured to
\nUpon receiving this RA message, the host performs the following actions:
\nAn example of the Stateful DHCPv6 configuration command on a Catalyst 9300 switch is as follows.
\nipv6 unicast-routing |
Compared to SLAAC + Stateless DHCPv6, the interface configuration here removes the ipv6 nd other-config-flag
and replaces it with the ipv6 nd managed-config-flag
command. This corresponds to setting the M-bit of the RA message header. The DHCPv6 server configuration adds two address prefix
commands to set the network prefix. Also, the ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise
configured for the interface specifies that the router does not include the 2001:ABCD:1:1::/64 prefix information option into the RA. So, this example host interface will not generate SLAAC addresses, but only two addresses from DHPCv6: a unique local address with the network prefix FD09:9:5:90::/64, and a global unicast address with the network prefix 2001:9:5:90::/64. The interface identifier for each of these two addresses is also specified by DHPCv6.
How to distinguish the source of dynamically assigned addresses for host interfaces? The method is simple. One thing to remember is that DHPCv6 does not send the network prefix length to the requestor, so the network prefix length of the addresses received from DHPCv6 is 128, while the network prefix length of the addresses generated by SLAAC will not be 128. See the following example of the wired0 interface on a Linux host:
\nifconfig wired0 |
We can immediately determine that the interface is using Stateful DHCPv6 address assignment, but also generates the SLAAC address with the same network prefix 2001:20::/64 received.
\nNote: DHPCv6 server also does not provide any IPv6 default gateway information. The host needs to be informed of the dynamic default gateway from the RA message.
\nThe following table shows the control bit combinations of RA messages concerning different address allocation and other configuration acquisition methods.
\nM-bit | \nO-bit | \nA-bit | \nHost Address | \nOther Configuration | \n
---|---|---|---|---|
0 | \n0 | \n0 | \nStatic Settings | \nManual Configuration | \n
0 | \n0 | \n1 | \nPrefix specified by RA, automatically generated | \nmanually configured | \n
0 | \n1 | \n0 | \nStatic Settings | \nDHCPv6 | \n
0 | \n1 | \n1 | \nPrefix specified by RA, automatically generated | \nDHCPv6 | \n
1 | \n0 | \n0 | \nStateful DHCPv6 | \nDHCPv6 | \n
1 | \n0 | \n1 | \nStateful DHCPv6 and/or automatically generated | \nDHCPv6 | \n
1 | \n1 | \n0 | \nStateful DHCPv6 | \nDHCPv6 | \n
1 | \n1 | \n1 | \nStateful DHCPv6 and/or automatically generated | \nDHCPv6 | \n
Summarize three dynamic allocation schemes:
\nAllocation Scheme | \nFeatures | \nAppiccation Scenarios | \n
---|---|---|
SLAAC | \nSimple and practical, fast deployment | \nSMB, Consumer Product Networking, Internet of Things (IoT) | \n
SLAAC + Stateless DHCPv6 | \nAuto Configuration, Extended Services | \nSMBs need additional network services | \n
Stateful DHCPv6 | \nCentralized management and control | \nLarge enterprises, institutions, and campus networks | \n
Note: Since IPv6 network interfaces can have multiple addresses (a link-local address, plus one or more unique local addresses and/or global unicast addresses), it becomes important how the source address is selected when establishing an external connection. RFC 6724 gives detailed IPv6 source address selection rules. In the development of embedded systems, the control plane and the data plane connected to the same remote device are often implemented by different functional components. For example, the control plane directly calls a Linux userspace socket to establish the connection, and the IPv6 source address used for the connection is selected by the TCP/IP stack, while the data plane directly implements data encapsulation processing and transmission in kernel space. In this case, the IPv6 source address selected by the control plane has to be synchronized to the data plane in time, otherwise, the user data might not be delivered to the same destination.
\nThe common IPv6 dynamic address assignment debugging and troubleshooting commands on Cisco routers and switches are listed in the following table.
\nCommand | \nDescription | \n
---|---|
show ipv6 interface brief | \nDisplays a short summary of IPv6 status and configuration for each interface | \n
show ipv6 interface [type] [num] | \nDisplays IPv6 and NDP usability status information for single interface | \n
show ipv6 interface [type] [num] prefix | \nDisplays IPv6 network prefix information for single interface | \n
show ipv6 dhcp pool | \nDisplay DHCPv6 configuration pool information | \n
show ipv6 dhcp binding | \nDisplays all automatic client bindings from the DHCPv6 server binding table | \n
show ipv6 dhcp interface [type] [num] | \nDisplay DHCPv6 interface information | \n
debug ipv6 nd | \nDebug IPv6 NDP protocol | \n
debug ipv6 dhcp | \nDebug DHCPv6 server | \n
The following console NDP protocol debug log shows that the router received an RS message from host FE80::5850:6D61:1FB:EF3A and responded with an RA message to the multicast address FF02::1 of all nodes in this network:
\nRouter# debug ipv6 nd |
And the next log shows an example of Stateless DHCPv6 observed after entering the debug ipv6 dhcp
debug command. Host FE80::5850:6D61:1FB:EF3A sends an INFORMATION-REQUEST message to the DHCPv6 server, which selects the source address FE80::C801:B9FF:FEF0:8 and sends a response message.
Router#debug ipv6 dhcp |
The following debug log of Stateful DHCPv6 shows the complete process of two message exchanges (SOLICIT/ADVERTISE, REQUEST/REPLY) on lines 1, 15, 16, and 26.
\nIPv6 DHCP: Received SOLICIT from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0 |
For complex cases where it is difficult to identify whether the problem is with the host, router, or DHCPv6 server, we recommend using the free open-source network packet analysis software Wireshark to capture packets of the entire process for analysis. While analyzing packets with Wireshark, you can apply the keyword filtering function.
\nFilter String | \nOnly Show | \n
---|---|
icmpv6.type=133 | \nICMPv6 RS | \n
icmpv6.nd.ra.flag | \nICMPv6 RA | \n
dhcpv6 | \nDHCPv6 packets | \n
We can either run Wireshark directly on the host side, or we can use the Switched Port Analyzer (SPAN) provided with the switch. Running on the network side, SPAN can collectively redirect packets from a given port to the monitor port running Wireshark for capturing. Cisco Catalyst 9300 Series switches also directly integrate with Wireshark software to intercept and analyze filtered packets online, making it very easy to use.
\nSample packet capture files for three allocation scheme are available here for download and study: slaac.pcap,stateless-dhcpv6.pcap,stateful-dhcpv6.pcap
\nAccurate and effective testing of IPv6 products is key to ensuring high interoperability, security, and reliability of IPv6 infrastructure deployments. The IPv6 Ready logo is an IPv6 testing and certification program created by the IPv6 Forum. Its goals are to define IPv6 conformance and interoperability test specifications, provide a self-testing toolset, establish Global IPv6 Test Centers and provide product validation services, and finally, issue IPv6 Ready logo.
\nIn May 2020, IPv6 Ready Logo Program published new version 5.0 test specifications:
\nAlong with these two new test specifications, the project team also affirmed two permanent changes:
\nNot surprisingly, the new version 5.0 core protocols test specification has a section dedicated to defining SLAAC test cases to validate this core IPv6 protocol.
\nIn the list below, the RFCs shown in bold are directly covered by the IPv6 Ready Version 5.0 Core Protocol Test Specification:
\nPurdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 1 exam.
\nBelow are extracted from the Spring 2024 CS24000 course syllabus:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) gcc -Wall -Werror -g -c abc.c -o xyz.o
\nExplanation of the options used:
-Wall
: Enable all warnings.-Werror
: Treat warnings as errors.-g
: Include debugging information in the output file.-c
: Compile or assemble the source files, but do not link.abc.c
: The source file to be compiled.-o xyz.o
: Specify the output file name (xyz.o).📝Notes: This output file xyz.o
is not executable since it is just the object file for a single c source file. We need to link to the standard library to make a executable file. If we force to run this xyz.o, it will return something like exec format error
.
(b) gcc xyz.o abc.o def.c -o prog
\nExplanation:
xyz.o
, abc.o
: Object files to be linked.def.c
: Source file to be compiled and linked.-o prog
: Specify the output file name (prog).(c) It advises gcc to include all warnings that help detect potentially problematic code.
(d) Many functions found in the string library (declared in string.h
) rely on null-terminated strings to operate correctly. Null-terminated strings are sequences of characters followed by a null character ('\\0'), which indicates the end of the string. Functions like strlen
, strcpy
, strcat
, strcmp
, and others expect null-terminated strings as input and produce null-terminated strings as output.
(e) In C, memory for a variable is allocated during its definition, not during its declaration.
\nDeclaration is announcing the properties of variable (no memory allocation), definition is allocating storages for a variable. Put pure declaration (struct, func prototype, extern) outside of the func, put definition inside func.
(f) size = 32
(There are 8 integer elements in this array, so 4 * 8.)
(g) 5 (Because ptr
is given the address of the 3rd element. So *(ptr - 1)
is the value of the 2nd element.)
(h) 12 (This is equal to *(ptr - *(ptr + 3))
, then *(ptr - 2)
. So finally it points to the 1st element of the array.)
(i) 8 (Because it mentions \"64-bit architecture\", so all addresses are of size 64-bit)
(a) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')
\n
struct resistor {
char id[ID_LEN];
float max_power;
int resistance;
};
(b) The answer is shown below:
\ntypedef struct resistor resistor_t;
(c) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')
\n
struct circuit_struct {
char name[CNAME_LEN];
resistor_t resistors[10];
};
(d) It will print sizeof = 920
. Explanation: 5 * (24 + 10 * (8 + 4 + 4)) = 920. This is because the id inside the resistor will occupy 8 bytes after padding to a multiple of 4.
struct circuit_struct circuit_board[5];
(e) The function can be written like the following:
\nint find_voltage(resistor_t r, int c) {
return (c * r.resistance);
}
The complete program is shown below
\n
|
The solution can be like this: (the include and struct definition are not necessary)
\n
|
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 2 exam.
\nBelow are extracted from the Spring 2024 CS24000 course syllabus:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Code without using array brackets:
\nint reverse(int *source, int *dest, int n) { |
In summary, the reverse function reverses the order of elements in the source array, stores them in the dest array, and calculates the sum of the reversed elements.
\n(b) The atomic weight of Aluminum is 26.981.
\n(c) Structure for a singly-linked list node containing an integer:
\ntypedef struct single_node { |
(d) Function to prepend a node to a singly-linked list:
\nvoid push(single_node_t **head, single_node_t *node) { |
(e) Function to remove the first node from a singly-linked list:
\nsingle_node_t *pop(single_node_t **head) { |
(a) Structure for a doubly-linked list node containing a string and an integer:
\ntypedef struct double_node { |
(b) Function to create a new doubly-linked list node:
\ndouble_node_t *create(char *name, int age) { |
(c) Function to delete a node from a doubly-linked list:
\nvoid delete(double_node_t *node) { |
(d) Function to insert a new node after a given node in a doubly-linked list:
\nvoid insert(double_node_t *node, double_node_t *new_node) { |
(a) Structure for a binary tree node:
\ntypedef struct tree_node { |
(b) The size of the tree_node_t structure on a 64-bit architecture system is 24 bytes (4 bytes for int, 1 byte for bool, and 8 bytes for each pointer).
\n(c) Function to mark a node as invalid:
\nvoid delete_node(tree_node_t *node) { |
(d) Function to remove a node from a binary tree (assuming it's not the root):
\nvoid free_node(tree_node_t *node) { |
(e) Recursive function to delete invalid nodes from a binary tree:
\nint flush_tree(tree_node_t *root, void (*my_del)(tree_node_t *)) { |
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Midterm exams.
\nBelow are extracted from the Summer 2023 CS24000 course homepage:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Consider the code snippet
\nint a, *b, *c; |
Explain in detail what is likely to happen if the code snippet is compiled and executed.
\n(b) What are the possible outcomes if the code snippet
\nchar r[4]; |
is compiled and executed? Explain your reasoning.
\n(c) Suppose we have a 2-D array, int x[2][3]
, wherein 6 integers are stored. What array expression is *(*(x+1)+2)
equivalent to, and why?
Problem 1 Solution
\n(a) The first printf()
outputs 3 since b
is a pointer to integer variable a. *c = 5
is likely to generate a segmentation fault since the code does not place a valid address in c before this assignment. The second printf()
is likely not reached due to a segmentation fault from *c = 5
which terminates the running program.
(b) There are two possible outcomes:
\nExplanation: If the memory location r[2]
contains EOS ('\\0') then the first outcome results. Otherwise, printf()
will continue to print byte values (not necessarily ASCII) until a byte containing 0 (i.e.,EOS) is reached.
(c) Equivalent to x[1][2]
.
Explanation: In our logical view of 2-D arrays: x
points to the location in memory where the beginning addresses of two 1-D integer arrays are located. Therefore x+1
points to the beginning address of the second 1-D integer array. *(x+1)
follows the pointer to the beginning address of the second 1-D integer array. *(x+1)+2
results in the address at which the third element of the second 1-D integer array is stored. *(*(x+1)+2)
accesses the content of the third element of the second 1-D integer array. Hence equivalent to x[1][2]
.
(a) Suppose main()
calls function
int abc(void) { |
three times. Explain what values are returned to main() in each of the three calls to abc()
.
(b) Suppose the code snippet
\nfloat m, **n; |
is compiled and executed. What is likely to happen, and why? How would you modify the code (involving printf()
calls) to facilitate ease of run-time debugging?
Problem 2 Solution
\n(a) Here are the three return values for each call and the explanation:
\na++
returns 4 before incrementing a
.b
becomes 2 since it preserves the previous value from the first call. So the if-statement checks 4 > 3. Hence a++
returns 4.b
becomes 3 at the beginning of the call, and the if-statement checks 4 > 4. So the program goes to the else-part which increments b
again and returns b
. Hence the function call returns 5.(b) Since we did not assign a valid address to n
, **n
is likely to reference an invalid address that triggers a segmentation fault which terminates the running program.
Although the first printf()
call was successful, 3.3 will likely will not be output to stdout (i.e., display) due to abnormal termination of the program and buffering by stdio library functions.
Adding a newline in the first printf(
) call, or calling fflush(stdout)
after the first printf()
call will force 3.3 in the stdout buffer to be flushed before the program terminates due to segmentation fault.
(a) Suppose you are supervising a team of C programmers. One of the team members is responsible for coding a function, int readpasswd(void)
, that reads from stdin a new password and checks that it contains upper case letters, special characters, etc. per company policy. The team member shows you part of the code
int readpasswd() { |
that reads a password from stdin and stores it in local variable secret
for further processing. Explain why you would be alarmed by the code. How would you rewrite to fix the problem in the code?
(b) Code main()
that reads a file, test.out, byte by byte using fgetc()
and counts how many bytes are ASCII characters. main()
outputs the count to stdout. Focus on making sure that your code is robust and does not crash unexpectedly.
Problem 3 Solution
\n(a) The scanf()
does not prevent user input that exceeds buffer size (100 characters) from overwriting memory in readpasswd()
's stack frame, potentially modifying its return address. This can lead to the execution of unintended code such as malware.
Alternate: The scanf()
functions can lead to a buffer overflow if used improperly. Here in this function, it does not have bound checking capability and if the input string is longer than 100 characters, then the input will overflow into the adjoining memory and corrupt the stack frame.
📝Notes: This is a major security flaw in scanf
family (scanf
, sscanf
, fscanf
..etc) esp when reading a string because they don't take the length of the buffer (into which they are reading) into account.
To fix this, the code should explicitly check that no more than 100 characters are read from stdin to prevent overflow over secret[100]
. This can be done by reading character by character using getchar()
in a loop until a newline is encountered or 100 characters have been read.
(b) A sample solution can be seen below
\n
|
Suppose you are given the code in main.c
\nint s[5]; |
which is compiled using gcc and executed. What are the two possible outcomes? Explain your answer.
\nBonus Problem Solution
\ns[5]
which may, or may not, corrupt program data and computation but does not crash the running program (i.e., silent run-time bug).s[5]
which exceeds the running program's valid memory, resulting in a segmentation fault.(a) Consider the code snippet
\nint x, *y, *z; |
Explain what is likely to happen if the code snippet is compiled and executed as part of main()
.
(b) Explain what the declarations of g and h mean:
\nchar *g(char *), (*h)(char *); |
For the two assignment statements to be meaningful
\nx = g(s); |
what must be the types of x
and y
? Provide the C statements for their type declarations.
Problem 1 Solution
\n(a) printf()
will output 10 (for x) and the address of x (in hexadecimal notation) which is contained in y. Assignment statement *z = 3
will likely trigger a segmentation fault since a valid address has not been stored in z.
(b) g is a function that takes a single argument that is a pointer to char (i.e., char *
), and g returns a pointer to char (i.e., address that points to char). h is a function pointer that takes a single argument that is a pointer to char, and h returns a value of type char.
x is a pointer to char, i.e., char *x
. y is a function that takes an argument that is a pointer to char and returns a value of type char, i.e., char y(char *)
.
(a) For the function
\nvoid fun(float a) { |
explain what is likely to happen if fun()
is called by main()
. Explain how things change if 1-D array x
is made to be global.
(b) What are potential issues associated with code snippet
\nFILE *f; |
Provide modified code that fixes the issues.
\nProblem 2 Solution
\n(a) Calling fun() will likely generate a stack smashing error. This is so since x is local to fun()
and overflowing the 1-D array (by 3 elements, i.e., 12 bytes) is likely to cause the canary (bit pattern) inserted by gcc (to guard the return address) to be changed. If x is made global, gcc does not insert a canary, hence stack smashing will not occur. However, overflowing x may, or may not, trigger a segmentation fault.
(b) Two potential issues:
\nfopen()
may fail and return NULL.fscanf()
may overflow 1-D array r if the character sequence in data.dat
exceeds 100 bytes.To fix these, do the following modifications:
\nf = fopen("data.dat", "r"); |
(a) A 2-D integer array, int d[100][200]
, declaration is restrictive in that it hardcodes the number of rows and columns to fixed values 100 and 200, respectively. Suppose two integers N and M are read from stdin that specify the number of rows and columns of a 2-D integer array which is then used to read N x M integers from stdin into main memory. Provide C code main()
that uses malloc() to achieve this task. Your code should be complete but for including header files.
(b) Provide code that reads a value of type unsigned int
from stdin, then uses bit processing techniques to count how many of the 32 bits contain bit value 0. Annotate your code to note what the different parts are doing.
Problem 3 Solution
\n(a) The complete code is shown below (Note we skip the NULL check for the return of malloc()
, add that after each such call if required)
int main() { |
📝Notes: Freeing memory of such a 2-D integer array also needs two steps:
\nvoid free_2d_array(int **array, int rows) { |
(b) The solution code can be seen below
\nunsigned int x, m = 1; |
Explain why printf(\"%d\", x)
passes argument x by value whereas scanf(\"%d\", &x)
passes the argument by reference. Can one code printf()
so that it passes x by reference? If so, why is it not done?
Bonus Problem Solution
\nprintf()
only needs a copy of the value of x to do its work of printing the value to stdout. scanf()
needs the address of x so that the value entered through stdin (by default, keyboard) can be stored at the address of x. Yes, since following the address of x allows printf() to access its value. It is not necessary to reveal the address of x to printf()
since it only requires its value.
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Final exams.
\nBelow are extracted from the Summer 2023 CS24000 course homepage:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Which statements in the code
\ntypedef struct friend { |
are problematic, likely to trigger segmentation fault? Augment the code by adding calls to malloc()
so that the bugs are fixed.
(b) Explain the difference between fun1 and fun2 which are declared as char *fun1(char *)
and char (*fun2)(char *)
, respectively. Code a function fun3 that takes a string as argument and returns the last character of the string. You may assume that the string is of length at least 1 (not counting EOS).
(c) Suppose a user enters the command, %/bin/cp file1 file2
, using a shell to copy the content of file1 to file2 on one of our lab machines. From the viewpoint of the shell, from where does it read its input /bin/cp file1 file2
? From the viewpoint of the app /bin/cp
which is coded in C, how does it access its input which specify the names of two files whose content is to be copied? Before calling execv()
what must the shell do to prepare the arguments of execv()
so that /bin/cp
has access to the two file names?
Problem 1 Solution
\n(a) Problematic:
\namigo->year = 2017; |
The reason is that the pointer amigo
has not been initialized to the address of any allocated memory space yet.
Agumentation:
\namigo = (friend_t *)malloc(sizeof(friend_t)); |
(b) fun1 takes as argument a pointer to char and returns a pointer to char. fun2 is a function pointer to a function that takes as argument a pointer to char and returns a value of type char.
\nchar fun3(char *s) { |
(c) Input /bin/cp file1 file2
is read from stdin.
main(int argc, char *argv)
of /bin/cp
accesses the two file names via argv[1]
and argv[2]
.
Assuming a variable s
is of type, char **s
, a shell must allocate sufficient memory for s
and copy /bin/cp
into s[0]1
, file1
into s[1]
, file2
into s[2]
, and set s[3]
to NULL.
(a) Code a function, unsigned int countdbl(long)
, that takes a number of type long
as input, counts the number of 0s in the bit representation of the input, and returns 0 if the count is an even number, 1 if odd. Use bit processing techniques to solve the problem. (b) gcc on our lab machine, by default, will insert code to detect stack smashing at run-time. What does gcc's code try to prevent from happening? In the case of reading input from stdin
(or file), what is a common scenario and programming mistake that can lead to stack smashing? Provide an example using scanf()
(or fscanf()
). What issound programming practice that prevents stack smashing?
Problem 2 Solution
\n(a)
\nunsigned int countdbl(long x) { |
(b) When a function is called by another function, gcc tries to detect if the return address has been corrupted and, if so, terminate the running program.This is to prevent the code from jumping to unintended code such as malware.A local variable of a function declared as a 1-D array overflows by input whose length is not checked when reading from stdin (or file).Example: a function contains code
\nchar buf[100]; |
which may overflow buf[]
since scanf()
does not check for length of the input.Sound practice: use functions to read from stdin (or file) that check for length.In the above example use fgets()
instead of scanf()
.
Code a function that takes variable number of arguments, double multnums(char *, ...)
, multiplies them and returns the result as a value of type double
. The fixed argument is a string that specifies how many arguments follow and their type (integer 'd' or float 'f'). For example, in the call multnums(\"dffd\", 3, 88.2, -100.5, 44)
, the format string \"dffd\" specifies that four arguments follow where the first character 'd' means the first argument in the variable argument list is of type integer, the second and third 'f' of type float, and the fourth 'd' of type integer. Forgo checking for errors and ignore header files. What would happen in your code if multnums
is called as multnums(\"dffd\", 3, 88.2, -100.5, 44, -92, 65)
? What about multnums(\"dffd\", 3, 88.2, -100.5)?
Explain your reasoning.
Problem 3 Solution
\ndouble multnums(char *a, ...) { |
When a C function is defined with a variable number of arguments, it typically uses the va_arg
, va_start
, and va_end
macros from the <stdarg.h>
header to handle the variable arguments.
If the input argument count does not match the format string provided to functions like printf
or scanf
, it can lead to undefined behavior and potentially cause crashes, memory corruption, or incorrect output/input.
Here are some specific scenarios that can occur when there is a mismatch between the input arguments and the format string:
\nSuppose an ASCII file contains lines where each line is a sequence of characters ending with \\n
but for the last line which ends because the end of file is reached. The goal of main() is to read and store the lines of the ASCII into a variable, char **x
, where malloc()
is used to allocate just enough memory to store the content of the file. Using only basic file I/O operations discussed in class, describe in words how your code would work to accomplish this task. Be detailed in how the arguments of malloc()
are determined to store the file content in x
.
Bonus Problem Solution
\nmalloc()
to allocate 1-D array, int *M
, of size r of type int. Open file, read byte by byte, counting for each line the number of bytes. Store the line lengthin 1-D array M. Close file.malloc()
for each line to allocate memory to store the bytes of each line. Point x to the 1-D array of pointers to char.A sample implementation (not required for this exam) is shown as below:
\n
|
Linear algebra provides mathematical tools to represent and analyze data and models in higher dimensions. It is essential for machine learning, computer graphics, control theory, and other scientific and engineering fields. Starting from this post, I will provide study guides and solutions to Purdue MA26500 exams in the last few semesters.
\nYou can’t learn too much linear algebra
— Benedict Gross (American mathematician, professor at the University of California San Diego and Harvard University, member of the National Academy of Sciences)
Purdue University is a world-renowned public research university that advances scientific, technological, engineering, and math discoveries. Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, as it is mandatory for undergraduate students of many science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
Let \\(A=\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\\),\\(B=\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}\\), and \\(C=AB^{-1}= \\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\\), then \\(a+b+c+d=\\)
\nProblem 1 Solution
\nBecause \\(C=AB^{-1}\\), we can multiple both sides by \\(B\\) and obtain \\(CB=AB^{-1}B=A\\). So \\[\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\n\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] Further, compute at the left side \\[\n\\begin{bmatrix}3a+4b & a+b\\\\3c+4d & c+d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] From here we can directly see \\(a+b=2\\) and \\(c+d=5\\), so \\(a+b+c+d=7\\). The answer is C.
\n⚠️Alert: There is no need to find the inverse of the matrix \\(B\\) and multiply the result with \\(A\\). Even if you can deduce the same answer, it is very inefficient and takes too much time.
\n\nLet \\(\\mathrm L\\) be a linear transformation from \\(\\mathbb R^3\\) to \\(\\mathbb R^3\\) whose standard matrix is \\(\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\2 &3 & k\\\\\\end{bmatrix}\\) where \\(k\\) is a real number. Find all values of \\(k\\) such that \\(\\mathrm L\\) is one-to-one.
\nProblem 2 Solution
\nFor this standard matrix, do elementary row operations below to achieve row echelon form.
\nFirst, add -2 times row 1 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &-1 &k-6\\\\\\end{bmatrix}\\] Then add row 2 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &0 &k-5\\\\\\end{bmatrix}\\] If \\(k=5\\), the equation \\(A\\mathbf x=\\mathbf b\\) has a free variable \\(x_3\\) and each \\(\\mathbf b\\) is the image of more than one \\(\\mathbf x\\). That is, \\(\\mathrm L\\) is not one-to-one. So the answer is E.
\n\nWhich of the following statements is/are always TRUE?
\nIf \\(A\\) is a singular \\(8\\times 8\\) matrix, then its last column must be a linear combination of the first seven columns.
Let \\(A\\) be a \\(5\\times 7\\) matrix such that \\(A\\cdot\\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\), and let \\(B\\) be a \\(7\\times 11\\) matrix such that \\(B\\cdot\\pmb x=\\pmb c\\) is consistent for any \\(\\pmb{c}∈\\mathbb{R}^7\\). Then, the matrix equation \\(AB\\cdot \\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\).
For any \\(m\\times n\\) matrix \\(A\\), the dimension of the null space of \\(A\\) equals the dimension of the null space of its transpose \\(A^T\\).
If \\(A\\) is an \\(m\\times n\\) matrix, then the set \\({A\\cdot\\pmb x|\\pmb x∈\\mathbb{R}^n}\\) is a subspace of \\(\\mathbb{R}^m\\).
Problem 3 Solution
\nFor (i), a singular matrix \\(A\\) is noninvertible and has \\(det(A)=0\\). By Theorem 8 of Section 2.3, the columns of \\(A\\) form a linearly dependent set. Denote \\(A=[\\pmb{v}_1\\cdots\\pmb{v}_8]\\), then there exist weights \\(c_1, c_2,\\cdots,c_8\\), not all zero, such that \\[c_1\\pmb{v}_1+c_2\\pmb{v}_2+\\cdots+c_8\\pmb{v}_8=\\pmb{0}\\] Does this imply that statement (i) is true? No! If \\(c_8\\) is 0, \\(\\pmb{v}_8\\) is NOT a linear combination of the columns \\(\\pmb{v}_1\\) to \\(\\pmb{v}_7\\).
\nFor (ii), since \\(AB\\cdot\\pmb x=A(B\\pmb{x})=A\\pmb c=\\pmb b\\). the consistency holds for the new \\(5\\times 11\\) matrix \\(AB\\) as well. It is true.
\nFor (iii), since \\(A\\) is a \\(m\\times n\\) matrix, \\(A^T\\) is a \\(n\\times m\\) matrix. From Section 2.9 Dimension and Rank, we know that \"If a matrix \\(A\\) has \\(n\\) columns, then \\(\\mathrm rank\\,A+\\mathrm{dim\\,Nul}\\,A= n\\).\" From this, we can list \\[\\begin{align}\n\\mathrm{dim\\,Nul}\\,A&=n-rank\\,A\\\\\n\\mathrm{dim\\,Nul}\\,A^T&=m-rank\\,A^T\n\\end{align}\\] As these two dimension numbers are not necessarily the same, (iii) is not true.
\nFor (iv), we can first review the definition of subspace. From Section 2.8 Subspaces of \\(\\mathbb R^n\\),
\n\n\nA subspace of \\(\\mathbb R^n\\) is any set \\(H\\) in \\(\\mathbb R^n\\) that has three properties:
\n
\na. The zero vector is in \\(H\\).
\nb. For each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u+\\pmb v\\) is in \\(H\\).
\nc. For each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in H.
Denote \\(\\pmb u=A\\pmb x\\), \\(\\pmb v=A\\pmb y\\), we have \\[\\begin{align}\nA\\cdot\\pmb{0}&=\\pmb{0}\\\\\n\\pmb u+\\pmb v&=A\\pmb{x}+A\\pmb{y}=A(\\pmb{x}+\\pmb{y})\\\\\nc\\pmb u&=cA\\pmb{x}=A(c\\pmb x)\n\\end{align}\\] All the results on the right side are in the set as well. This proves that (iv) is true.
\nAs both (ii) and (iv) are true, the answer is D.
\n\nCompute the determinant of the given matrix \\(\\begin{bmatrix}5 &7 &2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{bmatrix}\\)
\nProblem 4 Solution
\nNotice that the third column of the given matrix has all entries equal to zero except \\(a_{13}\\). Taking advantage of this, we can do a cofactor expansion down the third column, then continue to do cofactor expansion with the \\(3\\times3\\) submatrix \\[\\begin{align}\n\\begin{vmatrix}5 &7 &\\color{fuchsia}2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{vmatrix}&=(-1)^{1+3}\\cdot{\\color{fuchsia}2}\\cdot\\begin{vmatrix}0 &3 &-4\\\\\\color{blue}{-5} &-8 &3\\\\0 &5 &-6\\\\\\end{vmatrix}\\\\\n&=2\\cdot(-1)^{2+1}\\cdot({\\color{blue}{-5}})\\begin{vmatrix}3 &-4\\\\5 &-6\\\\\\end{vmatrix}=20\n\\end{align}\\] So the answer is B.
\n📝Notes:This problem is directly taken from the textbook. It is the Practice Problem of Section 3.1 Introduction to Determinants.
\n\nWhich of the following statements is always TRUE
\nA. If \\(A\\) is an \\(n\\times n\\) matrix with all entries being positive, then \\(\\det(A)>0\\).
\nB. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices with \\(\\det(A)>0\\) and \\(\\det(B)>0\\), then also \\(\\det(A+B)>0\\).
\nC. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices such that \\(AB=0\\), then both \\(A\\) and \\(B\\) are singular.
\nD. If rows of an \\(n\\times n\\) matrix \\(A\\) are linearly independent, then \\(\\det(A^{T}A)>0\\).
\nE. If \\(A\\) is an \\(n\\times n\\) matrix with \\(A^2=I_n\\), then \\(\\det(A)=1\\).
\nProblem 5 Solution
\nLet's analyze the statements one by one.
\nA is false. It is trivial to find a \\(2\\times 2\\) example to disprove it, such as \\[\\begin{vmatrix}1 &2\\\\3 &4\\\\\\end{vmatrix}=1\\times 4-2\\times 3=-2\\]
For B, as stated in Section 3 Properties of Determinants \"\\(\\det(A+B)\\) is not equal to \\(\\det(A)+\\det(B)\\), in general\", this statement is not necessarily true. On the contrary, we can have a simple case like \\(A=\\begin{bmatrix}1 &0\\\\0 &1\\\\\\end{bmatrix}\\) and \\(B=\\begin{bmatrix}-1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A+B)=0\\).
C is also false since B could be a zero matrix. If that is the case, A is not necessarily singular.
For D, first with the linearly independent property, we can see \\(\\det(A)\\neq 0\\). Secondary, the multiplicative property gives \\(\\det(A^{T}A)=\\det(A^{T})\\det(A)=(\\det(A))^2\\). So it is true that \\(\\det(A^{T}A) > 0\\).
For E, from \\(A^2=I_n\\), we can deduce \\(\\det(A^{2})=(\\det(A))^2=1\\), so \\(\\det(A)=\\pm 1\\). For example, if \\(A=\\begin{bmatrix}1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A)=-1\\). This statement is false.
So we conclude that the answer is D.
\n\nLet \\(A=\\begin{bmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{bmatrix}\\) and let its inverse \\(A^{-1}=[b_{ij}]\\). Find \\(b_{12}\\)
\nProblem 6 Solution
\nAccording to Theorem 8 of Section 3.3, \\(A^{-1}=\\frac{\\large{1}}{\\large{\\mathrm{det}\\,A}}\\mathrm{adj}\\,A\\). Here the adjugate matrix \\(\\mathrm{adj}\\, A\\) is the transpose of the matrix of cofactors. Hence \\[b_{12}=\\frac{C_{21}}{\\mathrm{det}\\,A}\\]
\nFirst computer the cofactor \\[C_{21}=(-1)^{2+1}\\begin{vmatrix}2 &6\\\\8 &10\\end{vmatrix}=(-1)\\cdot(20-48)=28\\] Now computer the determinant efficiently with row operations (Theorem 3 of Section 3.2) for \\(A\\) \\[\n{\\mathrm{det}\\,A}=\n\\begin{vmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{vmatrix}=\n\\begin{vmatrix}1 &2 &6\\\\0 &2 &-9\\\\0 &2 &-8\\\\\\end{vmatrix}=\n\\begin{vmatrix}\\color{blue}1 &2 &6\\\\0 &\\color{blue}2 &-9\\\\0 &0 &\\color{blue}1\\\\\\end{vmatrix}=\\color{blue}1\\cdot\\color{blue}2\\cdot\\color{blue}1=2\n\\] So \\(C_{21}=28/2=14\\), the answer is A.
\n\nLet \\(\\pmb{v_1}=\\begin{bmatrix}1\\\\2\\\\5\\\\\\end{bmatrix}\\), \\(\\pmb{v_2}=\\begin{bmatrix}-2\\\\-3\\\\1\\\\\\end{bmatrix}\\) and \\(\\pmb{x}=\\begin{bmatrix}-4\\\\-5\\\\13\\\\\\end{bmatrix}\\), and \\(\\pmb{B}=\\{\\pmb{v_1},\\pmb{v_2}\\}\\). Then \\(\\pmb B\\) is a basis for \\(H=\\mathrm{span}\\{\\mathbf{v_1,v_2}\\}\\). Determine if \\(\\pmb x\\) is in \\(H\\), and if it is, find the coordinate vector of \\(\\pmb x\\) relative to B.
\nProblem 7 Solution
\nBy definition in Section 1.3, \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) is the collection of all vectors that can be written in the form \\(c_1\\mathbf{v_1}+c_2\\mathbf{v_2}\\) with \\(c_1,c_2\\) scalars. So asking whether a vector \\(\\pmb x\\) is in \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) amounts to asking whether the vector equation \\[c_1\\pmb{v_1}+c_2\\pmb{v_2}=\\pmb{x}\\] has a solution. To answer this, row reduce the augmented matrix \\([\\pmb{v_1}\\,\\pmb{v_2}\\,\\pmb{x}]\\): \\[\n\\begin{bmatrix}1 &-2 &-4\\\\2 &-3 &-5\\\\5 &1 &13\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &11 &33\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &0 &0\\\\\\end{bmatrix}\\sim\n\\] We have a unique solution \\(c_1=2\\), \\(c_2=3\\). So the answer is E.
\n📝Notes:This problem is similar to Example 6 of Section 1.3 Vector Equations.
\n\nLet \\(T: \\mathbb R^2\\to\\mathbb R^3\\) be the linear tranformation for which \\[\nT\\left(\\begin{bmatrix}1\\\\1\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}3\\\\2\\\\1\\\\\\end{bmatrix}\\quad \\mathrm{and}\\quad\nT\\left(\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}1\\\\0\\\\2\\\\\\end{bmatrix}.\n\\] (4 points)(1) Let \\(A\\) be the standard matrix of \\(T\\), find \\(A\\).
\n(2 points)(2) Find the image of the vector \\(\\pmb u=\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}\\).
\n(4 points)(3) Is the vector \\(\\pmb b=\\begin{bmatrix}0\\\\-2\\\\5\\\\\\end{bmatrix}\\) in the range of \\(T\\)? If so, find all the vectors \\(\\pmb x\\) in \\(\\mathbb R^2\\) such that \\(T(\\pmb x)=\\pmb b\\)
\nProblem 8 Solution
\nReferring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, we know that \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] So if we can find \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\), we obtain \\(A\\). Remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\]
\nWe can use this property to find \\(A\\). First, it is trivial to see that \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =2\\begin{bmatrix}1\\\\1\\end{bmatrix}-\\begin{bmatrix}1\\\\2\\end{bmatrix}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =-\\begin{bmatrix}1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\2\\end{bmatrix}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=2T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}5\\\\4\\\\0\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=-T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}-2\\\\-2\\\\1\\end{bmatrix}\n \\end{align}\\] So \\(A\\) is \\(\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\).
The image of the vector \\(\\pmb u\\) can be obtained by \\(A\\pmb u\\), the result is \\[A\\pmb u=\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}=\\begin{bmatrix}-1\\\\-2\\\\3\\\\\\end{bmatrix}\\]
This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\[\\begin{bmatrix}5 &-2 &0\\\\4 &-2 &-2\\\\0 &1 &5\\end{bmatrix}\\] This has unique solution \\(\\begin{bmatrix}2\\\\5\\\\\\end{bmatrix}\\). So the vector \\(\\pmb b\\) is in the span of \\(T\\).
Consider the linear system \\[\n\\begin{align}\nx + 2y +3z &= 2\\\\\ny+az &= -4\\\\\n2x+5y+a^{2}z &= a-3\n\\end{align}\n\\] (4 points)(1) Find a row echelon form for the augmented matrix of the system.
\n(2 points)(2) For which value(s) of \\(a\\) does this system have a infinite number of solutions?
\n(2 points)(3) For which value(s) of \\(a\\) does this system have no solution?
\n(2 points)(4) For which value(s) of \\(a\\) does this system have a unique solution?
\nProblem 9 Solution
\nThe augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\2 &5 &a^2 &a-3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &1 &a^2-6 &a-7\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &0 &a^2-a-6 &a-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a2-a-6\\).
Look at the last row of the row echelon form, we can write it as \\((a-3)(a+2)z=(a-3)\\). Obviously if \\(a=3\\), \\(z\\) can be any number. So this system has an infinite number of solutions when \\(a=3\\).
If \\(a=-2\\), the equation becomes \\(0\\cdot z=-5\\). This is impossible. So the system is inconsistent and has no solution when \\(a=-2\\).
If \\(a\\neq -2\\) and \\(a\\neq 3\\),\\(z=\\frac 1 {a+2}\\), we can deduce unique solution for this system
Let \\[\nA=\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\n\\]
\n(5 points)(1) Find the REDUCED row echelon form for the matrix \\(A\\).
\n(5 points)(2) Find a basis for the null space of \\(A\\)
\nProblem 10 Solution
\nThe row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix. \\[\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &-1 &1 &-1 &3\\\\0 &-2 &1 &0 &3\\\\\\end{bmatrix}\\sim\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &-1 &1 &-3\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\] \\[\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\]
Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".
\nNow write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}1 &0 &0 &1 &2 &0\\\\0 &1 &0 &-1 &0 &0\\\\0 &0 &1 &-2 &3 &0\\\\\\end{bmatrix}\\]
\nThe general solution is \\(x_1=-x_4-2x_5\\), \\(x_2=x_4\\), \\(x_3=2x_4-3x_5\\), with \\(x_4\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-x_4-2x_5\\\\x_4\\\\2x_4-3x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]
📝Notes:This problem is similar to Example 6 of Section 2.8 Subspaces of \\(\\mathbb R^n\\). Read the solution for that example to get a deep understanding of this problem. Also pay attention to Example 7, Example 8, Theorem 13, and the Warning below this theorem in the same section.
\n\n\n\nWarning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). The columns of an echelon form \\(B\\) are often not in the column space of \\(A\\).
\n
This test set focuses on the following points of linear algebra:
\nAs can be seen, it has a very decent coverage of the basic ideas of linear algebra. So this set of exam problems provides a good test of students' knowledge of linear algebra.
\nOne thing I would like to highlight for preparing for the first exam of linear algebra is to have a complete understanding of two aspects of matrix equations. It is like two profiles of one object. As can be seen in the following snapshot taken from the textbook, a matrix equation can represent a linear combination of its column vectors. From a different viewpoint, it is used to describe the transformation that maps a vector in one space to a new vector in the other space.
\nHere comes the solution and analysis for Purdue MA 26500 Fall 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nLet \\[A=\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\] Let \\(a\\) be the rank of \\(A\\) and \\(b\\) be the nullity of \\(A\\), find \\(5b-3a\\)
\nProblem 1 Solution
\nDo row reduction as follows:
\n\\[\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\0 &2 &2 &-2 &0\\\\0 &3 &3 &-3 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &0 &2 &0 &-1\\\\0 &\\color{fuchsia}1 &1 &-1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]
\nSo we have 2 pivots, the rank is 2 and the nullity is 3. This results in \\(5b-3a=5\\cdot 3-3\\cdot 2=9\\).
\nThe answer is C.
\n\nLet \\(\\pmb u=\\begin{bmatrix}2\\\\0\\\\1\\end{bmatrix}\\), \\(\\pmb v=\\begin{bmatrix}3\\\\1\\\\0\\end{bmatrix}\\), and \\(\\pmb w=\\begin{bmatrix}1\\\\-1\\\\c\\end{bmatrix}\\) where \\(c\\) is a real number. The set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) is a basis for \\(\\mathbb R^3\\) provided that \\(c\\) is not equal
\nProblem 2 Solution
\nFor set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) to be a basis for \\(\\mathbb R^3\\), the three vectors should be linearly independent. Let's create a matrix with these vectors as columns, then do row reduction like below \\[\n\\begin{bmatrix}2 &3 &1\\\\0 &1 &-1\\\\1 &0 &c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\2 &3 &1\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &3 &1-2c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &0 &4-2c\\end{bmatrix}\n\\]
\nAs can be seen, we need 3 pivots to make these column vectors linearly independent. If \\(c\\) is 2, the last row above has all-zero entries, there would be only 2 pivots. So C cannot be 2 for these three vectors to be linearly independent.
\nThe answer is B.
\n\nWhich of the following statements is always TRUE?
\nProblem 3 Solution
\nPer definitions in 5.1 \"Eigenvectors and Eigenvalues\":
\n\n\nAn eigenvector of an \\(n\\times n\\) matrix \\(A\\) is a nonzero vector \\(\\pmb x\\) such that \\(A\\pmb x=\\lambda\\pmb x\\) for some scalar \\(\\lambda\\). A scalar \\(\\lambda\\) is called an eigenvalue of \\(A\\) if there is a nontrivial solution \\(\\pmb x\\) of \\(A\\pmb x=\\lambda\\pmb x\\); such an \\(\\pmb x\\) is called an eigenvector corresponding to \\(\\lambda\\).
\n
Statement A is missing the \"nonzero\" keyword, so it is NOT always TRUE.
\nFor Statement B, given \\(A\\pmb v=2\\pmb v\\), we can obtain \\(A(\\pmb{-v})=2(\\pmb{-v})\\). The eigenvalue is still 2, not \\(-2\\). This statement is FALSE.
\nStatement C involves the definition of Similarity. Denote \\(P=B^{-1}AB\\), we have \\[BPB^{-1}=BB^{-1}ABB^{-1}=A\\] So \\(A\\) and \\(P\\) are similar. Similar matrices have the same eigenvalues (Theorem 4 in Section 5.2 \"The Characteristic Equation\"). Statement C is FALSE
\n\n\nThis can be proved easily, as seen below \\[\\begin{align}\n\\det (A-\\lambda I)&=\\det (BPB^{-1}-\\lambda I)=\\det (BPB^{-1}-\\lambda BB^{-1})\\\\\n &=\\det(B)\\det(P-\\lambda I)\\det(B^{-1})\\\\\n &=\\det(B)\\det(B^{-1})\\det(P-\\lambda I)\n\\end{align}\\] Since \\(\\det(B)\\det(B^{-1})=\\det(BB^{-1})=\\det I=1\\), we see that \\(\\det (A-\\lambda I)=\\det(P-\\lambda I)\\). ■
\n
For Statement D, given \\(A\\pmb x=\\lambda\\pmb x\\), we can do the following deduction \\[A^2\\pmb x=AA\\pmb x=A\\lambda\\pmb x=\\lambda A\\pmb x=\\lambda^2\\pmb x\\] So it is always TRUE that \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).
\nStatement E is FALSE. An eigenvalue \\(-5\\) means matrix \\(B-(-5)I\\) is not invertible since \\(\\det(B-(-5)I)=\\det(B+5I)=0\\). But the statement refers to a different matrix \\(B-5I\\).
\nThe answer is D.
\n\nLet \\(\\mathbb P_3\\) be the vector space of all polynomials of degree at most 3. Which of the following subsets are subspaces of \\(\\mathbb P_3\\)?
\nProblem 4 Solution
\nPer the definition of Subspace in Section 4.1 \"Vector Spaces and Subspaces\"
\n\n\nA subspace of a vector space \\(V\\) is a subset \\(H\\) of \\(V\\) that has three properties:
\n
\na. The zero vector of \\(V\\) is in \\(H\\).
\nb. \\(H\\) is closed under vector addition. That is, for each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u + \\pmb v\\) is in \\(H\\).
\nc. \\(H\\) is closed under multiplication by scalars. That is, for each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in \\(H\\).
So to be qualified as the subspace, the subset should have all the above three properties. Denote the polynomials as \\(p(x)=a_0+a_1x+a_2x^2+a_3x^3\\).
\n(i) Since \\(p(0)=p(1)\\), we have \\(a_0=a_0+a_1+a_2+a_3\\), so \\(a_1+a_2+a_3=0\\).
\nThis proves that set (i) is a subspace of \\(\\mathbb P_3\\).
(ii) From \\(p(0)p(1)=0\\), we can deduce that \\(a_0(a_0+a_1+a_2+a_3)=0\\). So any polynomial in this set should satisfy this condition.
\nThis proves that set (ii) is NOT a subspace of \\(\\mathbb P_3\\).
(iii) It is easy to tell that this set is NOT a subspace of \\(\\mathbb P_3\\). If we do multiplication by floating-point scalars, the new polynomial does not necessarily have an integer coefficient for each term and might not be in the same set.
So the answer is A.
\n\nConsider the differential equation \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}1 &3\\\\-2 &2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\].
\nThen the origin is
\nProblem 5 Solution
\nFirst, write the system as a matrix differential equation \\(\\pmb x'(t)=A\\pmb x(t)\\). We learn from Section 5.7 \"Applications to Differential Equations\" that each eigenvalue–eigenvector pair provides a solution.
\nNow let's find out the eigenvalues of \\(A\\). From \\(\\det (A-\\lambda I)=0\\), we have \\[\\begin{vmatrix}1-\\lambda &3\\\\-2 &2-\\lambda\\end{vmatrix}=\\lambda^2-3\\lambda+8=0\\] This only gives two complex numbers as eigenvalues \\[\\lambda=\\frac{3\\pm\\sqrt{23}i}{2}\\]
\nReferring to the Complex Eigenvalues discussion at the end of this section, \"the origin is called a spiral point of the dynamical system. The rotation is caused by the sine and cosine functions that arise from a complex eigenvalue\". Because the complex eigenvalues have a positive real part, the trajectories spiral outward.
\nSo the answer is D.
\n\n\n\nRefer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:
\n\n\n
\n\n \n\n\nEigenvalues \nTrajectories \n\n \n\\(\\lambda_1>0, \\lambda_2>0\\) \nRepeller/Source \n\n \n\\(\\lambda_1<0, \\lambda_2<0\\) \nAttactor/Sink \n\n \n\\(\\lambda_1<0, \\lambda_2>0\\) \nSaddle Point \n\n \n\\(\\lambda = a\\pm bi, a>0\\) \nSpiral (outward) Point \n\n \n\\(\\lambda = a\\pm bi, a<0\\) \nSpiral (inward) Point \n\n \n\n\\(\\lambda = \\pm bi\\) \nEllipses (circles if \\(b=1\\)) \n
Which of the following matrices are diagonalizable over the real numbers?
\nProblem 6 Solution
\nThis problem tests our knowledge of Theorem 6 of Section 5.3 \"Diagonalization\":
\n\n\nAn \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n
So let's find out the eigenvalues for each matrix:
\nNow we can see that (i), (iii), and (iv) have distinct eigenvalues, they are diagonalizable matrices.
\nSo the answer is C.
\n\nA real \\(2\\times 2\\) matrix \\(A\\) has an eigenvalue \\(\\lambda_1=2+i\\) with corresponding eigenvector \\(\\pmb v_1=\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\). Which of the following is the general REAL solution to the system of differential equations \\(\\pmb x'(t)=A\\pmb x(t)\\)
\nProblem 7 Solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. Hence we know that \\(\\lambda_2=2-i\\) and \\(\\pmb{v}_2=\\begin{bmatrix}3+i\\\\4-i\\end{bmatrix}\\). However, we do not need these two to find our solution here. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{(2+i)t}\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}(\\cos t+i\\sin t)\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}\\begin{bmatrix}(3\\cos t+\\sin t)+(3\\sin t-\\cos t)i\\\\(4\\cos t-\\sin t)+(4\\sin t+\\cos t)i\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+\nc_2 e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\]
\nSo the answer is E.
\n\nLet \\(T: M_{2\\times 2}\\to M_{2\\times 2}\\) be a linear map defined as \\(A\\mapsto A+A^T\\).
\n(2 points) (1) Find \\(T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})\\)
\n(4 points) (2) Find a basis for the range of \\(T\\).
\n(4 points) (3) Find a basis for the kernel of \\(T\\).
\nProblem 8 Solution
\nAs the mapping rule is \\(A\\mapsto A+A^T\\), we can directly write down the transformation as below \\[T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})=\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}+\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}^T=\\begin{bmatrix}2 &5\\\\5 &8\\end{bmatrix}\\]
If we denote the 4 entries of a \\(2\\times 2\\) matrix as \\(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\), the transformation can be written as \\[\\begin{align}\nT(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})\n&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}+\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}^T=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}\\\\\n&=2a\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}+(b+c)\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix}+2d\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\n\\end{align}\\] So the basis can be the set of three \\(3\\times 3\\) matrices like below \\[\n\\begin{Bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix},\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix},\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\\end{Bmatrix}\n\\]
The kernel (or null space) of such a \\(T\\) is the set of all \\(\\pmb u\\) in vector space \\(V\\) such that \\(T(\\pmb u)=\\pmb 0\\). Write this as \\[T(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\0 &0\\end{bmatrix}\\] This leads to \\(a=d=0\\) and \\(c=-b\\). So the original matrix \\(A\\) that satified this conditioncan be represented as \\(c\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\). This shows that \\(\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\) (or \\(\\begin{bmatrix}0 &-1\\\\1 &0\\end{bmatrix}\\)) is the basis for the null space of \\(T\\).
(6 points) (1) Find all the eigenvalues of matrix \\(A=\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}\\), and find a basis for the eigenspace corresponding to each of the eigenvalues.
\n(4 points) (2) Find an invertible matrix \\(P\\) and a diagonal matrix \\(D\\) such that \\[\n\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}=PDP^{-1}\n\\]
\nProblem 9 Solution
\n(4 points) (1) Find the eigenvalues and corresponding eigenvectors of the matrix \\[\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\]
\n(2 points) (2) Find a general solution to the system of differential equations \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\]
\n(4 points) (3) Let \\(\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\\) be a particular soilution to the initial value problem \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix},\n\\begin{bmatrix}x(0)\\\\y(0)\\end{bmatrix}=\\begin{bmatrix}3\\\\7\\end{bmatrix}.\n\\] Find \\(x(1)+y(1)\\).
\nProblem 10 Solution
\nHere is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \nBook Sections | \n
---|---|---|
1 | \nThe Rank Theorem | \n4.6 \"Rank\" | \n
2 | \nLinear dependence, Invertible Matrix Theorem | \n4.3 \"Linearly Independent Sets; Bases\", 4.6 \"Rank\" | \n
3 | \nEigenvectors and Eigenvalues | \n5.1 \"Eigenvectors and Eigenvalues\" | \n
4 | \nVector Spaces and Subspaces | \n4.1 \"Vector Spaces and Subspaces\" | \n
5 | \nEigenfunctions of the Differential Equation | \n5.7 \"Applications to Differential Equations\" | \n
6 | \nThe Diagonalization Theorem, Diagonalizing Matrices | \n5.3 \"Diagonalization\" | \n
7 | \nComplex Eigenvalues and Eigenvectors | \n5.5 \"Complex Eigenvalues\" | \n
8 | \nKernel and Range of a Linear Transformation | \n4.2 \"Null Spaces, Column Spaces, and Linear Transformations\" | \n
9 | \nEigenvalues, Basis for Eigenspace, Diagonalizing Matrices | \n5.1 \"Eigenvectors and Eigenvalues\", 5.3 \"Diagonalization\" | \n
10 | \nEigenvectors and Eigenvalues | \n5.1 \"Eigenvectors and Eigenvalues\", 5.7 \"Applications to Differential Equations\" | \n
Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices).
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 Final exam covers all the topics from Chapter 1 to Chapter 7 Sections 1 in the textbook. This is a two-hour comprehensive common final exam given during the final exam week. There are 25 multiple-choice questions on the final exam.
\nProblem 1 Solution
\nStart with the augmented matrix of the system, do row reduction like below
\n\\[\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\2&0&-2&14\\\\3&2&1&3a\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&-4&-8&3a-48\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&0&0&3a-30\\end{array}\\right]\n\\]
\nClearly, this system of equations is consistent when \\(a=10\\). So the answer is B.
\n\nProblem 2 Solution
\nAccording to the properties of determinants:
\n\n\nLet A be a square matrix.
\n
\na. If a multiple of one row of \\(A\\) is added to another row to produce a matrix \\(B\\),then \\(\\det B =\\det A\\).
\nb. If two rows of \\(A\\) are interchanged to produce \\(B\\), then \\(\\det B=-\\det A\\).
\nc. If one row of A is multiplied by \\(k\\) to produce B, then \\(\\det B=k\\cdot\\det A\\).
Also since \\(\\det A^T=\\det A\\), a row operation on \\(A^T\\) amounts to a column operation on \\(A\\). The above property is true for column operations as well.
\nWith these properties in mind, we can do the following
\n\\[\\begin{align}\n\\begin{vmatrix}d&2a&g+d\\\\e&2b&h+e\\\\f&2c&i+f\\end{vmatrix}\n&=2\\times \\begin{vmatrix}d&a&g+d\\\\e&b&h+e\\\\f&c&i+f\\end{vmatrix}=\n 2\\times \\begin{vmatrix}d&a&g\\\\e&b&h\\\\f&c&i\\end{vmatrix}=\n 2\\times (-1)\\times \\begin{vmatrix}a&d&g\\\\b&e&h\\\\c&f&i\\end{vmatrix}\\\\\n&=(-2)\\times \\begin{vmatrix}a&b&c\\\\d&e&f\\\\g&h&i\\end{vmatrix}=(-2)\\times 1=-2\n\\end{align}\\]
\nSo the answer is A.
\n\nProblem 3 Solution
\nDenote \\(A=BCB^{-1}\\), it can be seen that \\[\\det A=\\det BCB^{-1}=\\det B\\det C\\det B^{-1}=\\det (BB^{-1})\\det C=\\det C\\]
\nThus we can directly write down the determinant calculation process like below (applying row operations) \\[\n\\begin{vmatrix}1&2&3\\\\1&4&5\\\\-1&3&7\\end{vmatrix}=\n\\begin{vmatrix}1&2&3\\\\0&2&2\\\\0&5&10\\end{vmatrix}=\n1\\times (-1)^{1+1}\\begin{vmatrix}2&2\\\\5&10\\end{vmatrix}=\n1\\times (2\\times 10-2\\times 5)=10\n\\]
\nSo the answer is B.
\n\nProblem 4 Solution
\nProblem 5 Solution
\nProblem 6 Solution
\nNote the trace of a square matrix \\(A\\) is the sum of the diagonal entries in A and is denoted by tr \\(A\\).
\nRemember the formula for inverse matrix \\[\nA^{-1}=\\frac{1}{\\det A}\\text{adj}\\;A=[b_{ij}]\\\\\nb_{ij}=\\frac{C_{ji}}{\\det A}\\qquad C_{ji}=(-1)^{i+j}\\det A_{ji}\n\\] Where \\(\\text{adj}\\;A\\) is the adjugate of \\(A\\), \\(C_{ji}\\) is a cofactor of \\(A\\), and \\(A_{ji}\\) denotes the submatrix of \\(A\\) formed by deleting row \\(j\\) and column \\(i\\).
\nNow we can find the answer step-by-step:
\nCalculate the determinant of \\(A\\) \\[\n\\begin{vmatrix}1&2&7\\\\1&3&12\\\\2&5&20\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&1&6\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&0&1\\end{vmatrix}=1\n\\]
Calculate \\(b_{11}\\), \\(b_{22}\\), and \\(b_{33}\\) \\[\nb_{11}=\\frac{C_{11}}{1}=\\begin{vmatrix}3&12\\\\5&20\\end{vmatrix}=0\\\\\nb_{22}=\\frac{C_{22}}{1}=\\begin{vmatrix}1&7\\\\2&20\\end{vmatrix}=6\\\\\nb_{33}=\\frac{C_{33}}{1}=\\begin{vmatrix}1&2\\\\1&3\\end{vmatrix}=1\n\\]
Get the trace of \\(A^{-1}\\) \\[\\text{tr}\\;A^{-1}=b_{11}+b_{22}+b_{33}=0+6+1=7\\]
So the answer is C.
\n\nProblem 7 Solution
\nProblem 8 Solution
\nProblem 9 Solution
\nProblem 10 Solution
\nProblem 11 Solution
\nProblem 12 Solution
\nProblem 13 Solution
\nProblem 14 Solution
\nProblem 15 Solution
\nProblem 16 Solution
\nProblem 17 Solution
\nProblem 18 Solution
\nProblem 19 Solution
\nProblem 20 Solution
\nProblem 21 Solution
\nProblem 22 Solution
\nProblem 23 Solution
\nProblem 24 Solution
\nProblem 25 Solution
\n\nMA 265 Fall 2022 Final\n
\n\n\nMA 265 Sprint 2023 Final\n
\n\n\nMA 265 Fall 2019 Final\n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/","content":"Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nProblem 1 Solution
\nA From the following \\[c_1(\\pmb u+\\pmb v)+c_2(\\pmb v+\\pmb w)+c_3\\pmb w=c_1\\pmb u+(c_1+c_2)\\pmb v+(c_2+c_3)\\pmb w\\] it can be concluded that if \\(\\pmb u\\), \\(\\pmb v\\), and \\(\\pmb w\\) are linearly independent, it is always true that \\(\\pmb u+\\pmb v\\), \\(\\pmb v+\\pmb w\\), and \\(\\pmb w\\) are linearly independent. So this statement is always true.
\nB This is also true. If the number of vectors is greater than the number of entries (\\(n\\) here), the transformation matrix has more columns than rows. The column vectors are not linearly independent.
\nC This is always true per the definition of basis and spanning set.
\nD If the nullity of a \\(m\\times n\\) matrix \\(A\\) is zero, \\(rank A=n\\). This means there the column vectors form a linearly independent set, and there is one pivot in each column. However, this does not mean \\(A\\pmb x=\\pmb b\\) has a unique solution for every \\(\\pmb b\\). For example, see the following augmented matrix in row echelon form (after row reduction): \\[\n\\begin{bmatrix}1 &\\ast &\\ast &b_1\\\\0 &1 &\\ast &b_2\\\\0 &0 &1 &b_3\\\\0 &0 &0 &b_4\\end{bmatrix}\n\\] If \\(b_4\\) is not zero, the system is inconsistent and there is no solution. So this one is NOT always true.
\nE This is always true since the rank of a \\(m\\times n\\) matirx is always in the range of \\([0, n]\\).
\nSo the answer is D.
\n\nProblem 2 Solution
\nDenote \\(3\\times 3\\) matrix as \\(A=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\), then from the given condition we can get \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\\\\n\\implies&\\begin{bmatrix}a &b &c\\\\2d &2e &2f\\\\3g &3h &3i\\end{bmatrix}=\\begin{bmatrix}a &2b &3c\\\\d &2e &3f\\\\g &2h &3i\\end{bmatrix}\\\\\n\\implies&A=\\begin{bmatrix}a &0 &0\\\\0 &2e &0\\\\0 &0 &3i\\end{bmatrix}=a\\begin{bmatrix}1 &0 &0\\\\0 &0 &0\\\\0 &0 &0\\end{bmatrix}+\n2e\\begin{bmatrix}0 &0 &0\\\\0 &1 &0\\\\0 &0 &0\\end{bmatrix}+\n3i\\begin{bmatrix}0 &0 &0\\\\0 &0 &0\\\\0 &0 &1\\end{bmatrix}\n\\end{align}\\]
\nIt can be seen that there are three basis vectors for this subspace and the dimension is 3. The answer is A.
\nNotice the effects of left-multiplication and right-multiplication of a diagonal matrix.
\n\nProblem 3 Solution
\nFrom \\(\\det A-\\lambda I\\), it becomes \\[\\begin{align}\n\\begin{vmatrix}4-\\lambda &0 &0 &0\\\\-2 &-1-\\lambda &0 &0\\\\10 &-9 &6-\\lambda &a\\\\1 &5 &a &3-\\lambda\\end{vmatrix}\n&=(4-\\lambda)(-1-\\lambda)((6-\\lambda)(3-\\lambda)-a^2)\\\\\n&=(\\lambda-4)(\\lambda+1)(\\lambda^2-9\\lambda+18-a^2)\n\\end{align}\\]
\nSo if 2 is an eigenvalue for the above, the last multiplication item becomes \\((2^2-18+18-a^2)\\) that should be zero. So \\(a=\\pm 2\\).
\nThe answer is E.
\n\nProblem 4 Solution
\n(i) Referring to Theorem 4 in Section 5.2 \"The Characteristic Equation\" >If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).
\nSo this statement must be TRUE.
\n(ii) If the columns of \\(A\\) are linearly independent, \\(A\\pmb x=\\pmb 0\\) only has trivial solution and \\(A\\) is an invertible matrix. This also means \\(\\det A\\neq 0\\). From here, it must be TRUE that \\(\\det A-0 I\\neq 0\\). So 0 is NOT an eigenvalue of \\(A\\). This statement is FALSE.
\n(iii) A matrix \\(A\\) is said to be diagonalizable if it is similar to a diagonal matrix, which means that there exists an invertible matrix \\(P\\) such that \\(P^{-1}AP\\) is a diagonal matrix. In other words, \\(A\\) is diagonalizable if it has a linearly independent set of eigenvectors that can form a basis for the vector space.
\nHowever, the condition for diagonalizability does not require that all eigenvalues be nonzero. A matrix can be diagonalizable even if it has one or more zero eigenvalues. For example, consider the following matrix: \\[A=\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\n=\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\] This matrix has one nonzero eigenvalue (\\(λ = 1\\)) and one zero eigenvalue (\\(λ = 0\\)). However, it is diagonalizable with the identity matrix as \\(P\\) and \\(D=A\\).
\nSo this statement is FALSE.
\n(iv) Similar matrices have the same eigenvalues (with the same multiplicities). Hence \\(-\\lambda\\) is also an eigenvalue of \\(B\\). Then we have \\(B\\pmb x=-\\lambda\\pmb x\\). From this, \\[\nBB\\pmb x=B(-\\lambda)\\pmb x=(-\\lambda)B\\pmb x=(-\\lambda)(-\\lambda)\\pmb x=\\lambda^2\\pmb x\n\\] So \\(\\lambda^2\\) is an eigenvalue of \\(B^2\\). Following the same deduction, we can prove that \\(\\lambda^4\\) is an eigenvalue of \\(B^4\\). This statement is TRUE.
\n(v) Denote \\(A=PBP^{-1}\\). If \\(A\\) is diagonizible, then \\(A=QDQ^{-1}\\) for some diagonal matrix \\(D\\). Now we can also write down \\[B=P^{-1}AP=P^{-1}QDQ^{-1}P=(P^{-1}Q)D(P^{-1}Q)^{-1}\\] This proves that \\(B\\) is also diagonalizable. This statement is TRUE.
\nSince statements (ii) and (iii) are FALSE and the rest are TRUE, the answer is D.
\n\nProblem 5 Solution
\n(i) Obviously \\(x=y=z=0\\) does not satisfy \\(x+2y+3z=1\\), this subset is NOT a subspace of \\(\\mathbb R^3\\).
\n(ii) This subset is a subspace of \\(\\mathbb R^3\\) since it has all the three properties of subspace:
\n(iii) Here \\(p(t)=a_0+a_1t+a_2t^2+a_3t^3\\) and \\(a_3\\neq 0\\). This set does not include zero polynomial. Besides, if \\(p_1(t)=t^3+t\\) and \\(p_2(t)=-t^3+t\\), then \\(p_1(t)+p_2(t)=2t\\). This result is not a polynomial of degree 3. So this subset is NOT closed under vector addition and is NOT a subspace of \\(\\mathbb P_3\\).
\n(iv) The condition \\(p(2)=0\\) means \\(a_0+2a_1+4a_3+8a_3=0\\). It does include zero polynomial. It also satisfies the other two properties because \\[\\begin{align}\ncp(2)&=c(a_0+2a_1+4a_3+8a_3)=0\\\\\np_1(2)+p_2(2)&=(a_0+2a_1+4a_3+8a_3)+(b_0+2b_1+4b_3+8b_3)=0\n\\end{align}\\] So this set is indeed a subset of \\(\\mathbb P_3\\).
\nSince we have (ii) and (iv) be our choices, the answer is A.
\n\nProblem 6 Solution
\n\\[\n\\begin{vmatrix}4-\\lambda &2\\\\3 &5-\\lambda\\end{vmatrix}=\\lambda^2-9\\lambda+20-6=(\\lambda-2)(\\lambda-7)\n\\]
\nSo there are two eigenvalues 2 and 7. Since both are positive, the origin is a repeller. The answer is B.
\n\nProblem 7 Solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{1+i}\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t(\\cos t+i\\sin t)\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t\\begin{bmatrix}\\cos t+2\\sin t+i(\\sin t-2\\cos t)\\\\3\\cos t-4\\sin t+i(3\\sin t+4\\cos t)\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}\\cos t+2\\sin t\\\\3\\cos t-4\\sin t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\sin t-2\\cos t\\\\3\\sin t+4\\cos t\\end{bmatrix}\\]
\nThe answer is A.
\n\n
Problem 8 Solution
\n(1) Since \\(p(t)=at^2+bt+c\\), its derivative is \\(p'(t)=2at+b\\). So we can have \\[\nT(at^2+bt+c)=\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}\n\\]
\n(2) From the result of (1) above, we can directly write down that \\(c=1\\) and \\(b=2\\). Then because \\(2a+b=4\\), \\(a=2\\). So \\(p(t)=t^2+2t+1\\).
\n(3) Write down this transformation as the parametric vector form like below \\[\n\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}=\na\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix}+\nb\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix}+\nc\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\] So a basis for the range of \\(T\\) is \\[\n\\begin{Bmatrix}\n\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix},\n\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix},\n\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\end{Bmatrix}\n\\]
\n\n
Problem 9 Solution
\n(1) First find all the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}2-\\lambda &0 &0\\\\1 &5-\\lambda &1\\\\-1 &-3 &1-\\lambda\\end{vmatrix}&=(2-\\lambda)\\begin{vmatrix}5-\\lambda &1\\\\-3 &1\\lambda\\end{vmatrix}\\\\\n&=(2-\\lambda)(\\lambda^2-6\\lambda+5+3)\\\\\n&=(2-\\lambda)(\\lambda-2)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 2 with multiplicity and 4.
\nNow find out the eigenvector(s) for each eigenvalue
\nFor \\(\\lambda_1=\\lambda_2=2\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\-1 &-3 &-1\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\0 &0 &0\\end{bmatrix}\n\\] Convert this result to a parametric vector form with two free variables \\(x_2\\) and \\(x_3\\) \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\n\\begin{bmatrix}-3x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=\nx_2\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\n\\] So the basis for the eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
For \\(\\lambda_3=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}-2 &0 &0\\\\1 &1 &1\\\\-1 &-3 &-3\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &-2 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\n\\] This ends up with \\(x_1=0\\) and \\(x_2=-x_3\\). So the eigenvector is \\(\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\) or \\(\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\). The basis for the corresponding eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\) or \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\end{Bmatrix}\\).
(2) From the answers of (1), we can directly write down \\(P\\) and \\(D\\) as \\[\nP=\\begin{bmatrix}-3 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix},\\;\nD=\\begin{bmatrix}2 &0 &0\\\\0 &2 &0\\\\0 &0 &4\\end{bmatrix}\n\\]
\n\n
Problem 10 Solution
\n(1) First find the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}9-\\lambda &5\\\\-6 &-2-\\lambda\\end{vmatrix}\n&=\\lambda^2-7\\lambda-18-(-5)\\cdot 6\\\\\n&=\\lambda^2-7\\lambda+12\\\\\n&=(\\lambda-3)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 3 and 4.
\nFor \\(\\lambda_1=3\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}6 &5\\\\-6 &5\\end{bmatrix}\\sim\n\\begin{bmatrix}6 &5\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-5\\\\6\\end{bmatrix}\\).
Likewise, for \\(\\lambda_2=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}5 &5\\\\-6 &-6\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\).
(2) With the eigenvalues and corresponding eigenvectors known, we can apply them to the general solution formula \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So the answer is \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}-5\\\\6\\end{bmatrix}e^{3t}+c_2\\begin{bmatrix}-1\\\\1\\end{bmatrix}e^{4t}\n\\]
\n(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n-5c_1-c_2&=1\\\\\n6c_1+c_2&=0\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=-6\\). So \\(x(1)+y(1)=-5e^{3}+6e^4+6e^3-6e^4=e^3\\).
\n\nThis is the 3rd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Fall 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.
\nThere is hardly any theory which is more elementary [than linear algebra], in spite of the fact that generations of professors and textbook writers have obscured its simplicity by preposterous calculations with matrices.
— Jean Dieudonné (1906~1992, French mathematician, notable for research in abstract algebra, algebraic geometry, and functional analysis.)
Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm I covers the topics in Sections 1.1 – 3.3 of the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nHere are a few extra reference links for Purdue MA 26500:
\nProblem 1 Solution
\nBecause \\(C=B^{-1}A\\), we can left-multiply both sides by \\(B\\) and obtain \\(BC=BB^{-1}A=A\\). So \\[\n\\begin{bmatrix}0 & 1\\\\1 & 5\\\\\\end{bmatrix}\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] Further, compute matrix multiplication at the left side \\[\n\\begin{bmatrix}c &d\\\\a+5c &b+5d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] From here we can directly get \\(c=d=1\\), then \\(a=-2\\) and \\(b=-3\\). This leads to \\(a+b+c+d=-3\\).
\nThe answer is A.
\n\nProblem 2 Solution
\nThe reduced row echelon form has the same number of pivots as the original matrix. And the rank of a matrix \\(A\\) is just the number of pivot columns in \\(A\\). From these, we can deduce statement (iii) is true.
\nPer the Rank Theorem (rank \\(A\\) + dim Nul \\(A\\) = \\(n\\)), since \\(\\mathrm{Rank}(A)=\\mathrm{Rank}(R)\\), we obtain \\(\\mathrm{Nul}(A)=\\mathrm{Nul}(R)\\). So statement (i) is true as well.
\nFor a square matrix \\(A\\), suppose that transforming \\(A\\) to a matrix in reduced row-echelon form using elementary row operations \\(E_kE_{k−1}⋯E_1A=R\\). Taking the determinants of both sides, we get \\(\\det E_kE_{k−1}⋯E_1A=\\det R\\). Now, using the fact that the determinant of a product of matrices is the same as the product of the determinants of the matrices, we get that \\[\\det A=\\frac{\\det R}{\\det E_1⋯\\det E_k}\\]
\nAccording to the description in the \"Proofs of Theorems 3 and 6\" part in Section 3.2 Properties of Determinants, it is proven that \\(\\det E\\) would be either 1, -1, or a scalar. Taking all these into consideration, if \\(\\det R\\) is zero, \\(\\det A\\) must be zero. Statement (v) is true.
\n📝Notes:The reduced row echelon form of a square matrix is either the identity matrix or contains a row of 0's. Hence, \\(\\det R\\) is either 1 or 0.
\nNow look back at statement (ii), the column space of the matrix \\(A\\) is not necessarily equal to the column space of \\(R\\), because the reduced row echelon form could contain a row of 0's. In such a case, the spans of these two column spaces are different.
\nFor the same reason, we can conclude that the statement (iv) is false. Referring to Theorem 4 in Section 1.4 The Matrix Operation \\(A\\pmb x=\\pmb b\\) (check the \"Common Errors and Warnings\" in the end), \"For each \\(\\pmb b\\) in \\(\\pmb R^m\\), the equation \\(A\\pmb x=\\pmb b\\) has a solution\" is true if and only if \\(A\\) has a pivot position in every row (not column).
\nThe answer is A.
\n\nProblem 3 Solution
\nFirst, we can do row reduction to obtain the row echelon form of the standard matrix \\[\\begin{align}\n&\\begin{bmatrix}1 &a &a+1\\\\2 &a+2 &a-1\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\\\\\n\\sim&\\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &a(a-2) &(a+1)(a-2)\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &0 &-4a-2\\\\\\end{bmatrix}\n\\end{align}\\]
\nIf \\(a=2\\), the 2nd column is a multiple of the 1st column, so the columns of \\(A\\) are not linearly independent, then the transformation would not be one-to-one (Check Theorem 12 of Section 1.9 The Matrix of a Linear Transformation).
\nMoreover, if \\(a=-\\frac{1}{2}\\), the entries of the last row are all 0s. In such case, matrix \\(A\\) has only two pivots and \\(A\\pmb x=\\pmb 0\\) has non-trivial solutions, \\(L\\) is not one-to-one (See Theorem 11 of Section 1.9 The Matrix of a Linear Transformation).
\nSo the answer is C.
\n\nProblem 4 Solution
\nStatement A is wrong as none of these 3 vectors is a linear combination of the other two. They form a linearly independent set.
\nStatement B is wrong as we need 4 linearly independent vectors to span \\(\\mathbb R^4\\).
\nStatements C and D are also wrong because B is wrong. Not all vectors in \\(\\mathbb R^4\\) can be generated with a linear combination of these 3 vectors, and \\(A\\pmb x=\\pmb b\\) might have no solution.
\nStatements E is correct. It has a unique but trivial solution. Quoted from the textbook Section 1.7 Linear Independence:
\n\n\nThe columns of a matrix \\(A\\) are linearly independent if and only if the equation \\(A\\pmb x=\\pmb 0\\) has only the trivial solution.
\n
So the answer is E.
\n\nProblem 5 Solution
\nFrom the given condition, we know that \\(A\\) is a \\(m\\times n\\) matrix. So statement A is wrong.
\nStatement B is not necessarily true since \\(\\pmb b\\) could be outside of the range but still in the \\(\\mathbb R^m\\) as the codomain of \\(T\\). Statement E is also not true for the same reason.
\nStatement D is wrong. Since \\(m\\) is the row number of the matrix \\(A\\), rank \\(A=m\\) just means the number of pivots is equal to the row number. To have the column linearly independent, we need the pivot number to be the same as the column number.
\nNow we have only statement C left. If \\(m<n\\), the column vector set is linearly dependent. But \\(T\\) is one-to-one if and only if the columns of \\(A\\) are linearly independent. So \\(m<n\\) cannot be true.
\nThe answer is C.
\n\nProblem 6 Solution
\nThis is to solve the following equation system: \\[\n\\begin{bmatrix}2 &3\\\\1 &-1\\\\5 &4\\\\\\end{bmatrix}\n\\begin{bmatrix}x_1\\\\x_2\\\\\\end{bmatrix}=\n\\begin{bmatrix}1\\\\3\\\\6\\\\\\end{bmatrix}\n\\] Let's do the row reduction with the augmented matrix \\[\n\\begin{bmatrix}2 &3 &1\\\\1 &-1 &3\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\2 &3 &1\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-5\\\\0 &9 &-9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &1 &-1\\\\0 &0 &0\\\\\\end{bmatrix}\n\\]
\nThis yields the unique solution \\(x_1=2\\) and \\(x_2=-1\\). So the answer is B.
\n\nProblem 7 Solution
\nFirst, we can exclude E as it has a zero vector, and a vector set including a zero vector is always linearly dependent.
\nC has its column 2 equal to 2 times column 1. It is not linearly independent.
\nA is also wrong. It is easy to see that column 3 is equal to 2 times column 1 minus column 2.
\nB has zeros in row 3 of all four vectors. So all the vectors have only 3 valid entries. But we have 4 vectors. Referring to Theorem 8 of Section 1.7 Linear Independence, this is equivalent to the case that 4 vectors are all in 3D space. So there must be one vector that is a linear combination of the other 3. B is not the right answer.
\nD can be converted to the vector set \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\1\\\\0\\end{bmatrix},\n\\begin{bmatrix}0\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\] This is a linear independent vector set since we cannot get any column by linearly combining the other two.
\nSo the answer is D.
\n\n
Problem 8 Solution
\nStart with the augmented matrix and do row reduction \\[\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\3 &2 &2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &-1 &2-3a &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &0 &a(a-3) &a\\\\\\end{bmatrix}\n\\]
Apparently if \\(a=0\\), the last row has all zero entries, the system has one free variable and there are an infinite number of solutions.
If \\(a=3\\), the last row indicates \\(0=3\\), the system is inconsistent and has no solution.
If \\(a\\) is neither 3 nor 0, the row echelon form shows three pivots, thus the system has a unique solution.
Problem 9 Solution
\nThe sequence of row reduction to get the reduced row echelon form is shown below \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\2 &0 &-3 &-4 &5\\\\5 &0 &-6 &-1 &14\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &-1 &0 &-1\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &-2 &4\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]
From the reduced row echelon form, we can see that there are two pivots and three free variables \\(x_2\\), \\(x_4\\), and \\(x_5\\). So the system \\(A\\pmb x=\\pmb 0\\) becomes \\[\\begin{align}\nx_1-2x_4+4x_5&=0\\\\\nx_3+x_5&=0\n\\end{align}\\]
Now write the solution in parametric vector form. The general solution is \\(x_1=2x_4-4x_5\\), \\(x_3=-x_5\\). This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}2x_4-4x_5\\\\x_2\\\\-x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_2\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_4\\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\\begin{Bmatrix}\n \\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\end{Bmatrix}\\]
\n\n
Problem 10 Solution
\nFor computing the determinant of matrix \\(A\\) with the 1st column cofactor expansion, note that the only nonzero entry in column 1 is \\(a_{1,4}=2\\), so we have \\[\\begin{align}\n\\det A&=(-1)^{1+4}\\cdot 2\\cdot\\begin{vmatrix}1 &2 &3\\\\0 &\\color{fuchsia}3 &0\\\\1 &1 &1\\end{vmatrix}\\\\\n &=(-2)\\cdot 3\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=(-6)\\cdot(-2)=12\n\\end{align}\\]
From the adjugate of \\(A\\), we deduce the formula
\\[\\begin{align}\nb_{3,2}&=\\frac{C_{2,3}}{\\det A}=\\frac{1}{12}\\cdot(-1)^{2+3}\\begin{vmatrix}0 &1 &3\\\\0 &1 &1\\\\\\color{fuchsia}2 &0 &1\\end{vmatrix}\\\\\n&=\\frac{-1}{12}\\cdot(-1)^{3+1}\\cdot 2\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=\\frac{1}{3}\n\\end{align}\\]
\n\nHere is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \n
---|---|
1 | \nMatrix Multiplications, Inverse Matrix | \n
2 | \nColumn Space, Rank, Nul Space, Determinant, Pivot, Linear System Consistency | \n
3 | \nLinear Transformation, One-to-One Mapping | \n
4 | \nLinear Dependency, Vector Set Span \\(\\mathbb R^n\\), Unique Solution | \n
5 | \nLinear Transformation, One-to-One Mapping, Rank, Column Linear Independency, Vector Set Span \\(\\mathbb R^n\\) | \n
6 | \nBasis of Span \\({v_1, v_2}\\) | \n
7 | \nLinear Independency Vector Set | \n
8 | \nRow Echelon Form, Augmented Matrix, Linear System Solution Set and Consistency | \n
9 | \nReduced Row Echelon Form, Basis for the Null Space | \n
10 | \nDeterminant, Cofactor Expansion, Inverse Matrix, The Adjugate of Matrix | \n
As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.
\nHere are a few warnings collected from the textbook. It is highly recommended that students preparing for the MA 265 Midterm I exam review these carefully to identify common errors and know how to prevent them in the test.
\nThis is the 2nd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Spring 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.
\nMatrices act. They don't just sit there.
— Gilbert Strang (American mathematician known for his contributions to finite element theory, the calculus of variations, wavelet analysis and linear algebra.)
Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm I covers the topics of Sections 1.1 – 3.3 in the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nHere are a few extra reference links for Purdue MA 26500:
\nProblem 1 Solution
\nReferring to Section 3.2 Property of Determinants, we can do row and column operations to efficiently find the determinant of the given matrix.
\n\\[\\begin{align}\n\\begin{vmatrix}a &b &3c\\\\g &h &3i\\\\d+2a &e+2b &3f+6c\\\\\\end{vmatrix}&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d+2a &e+2b &3f+6c\\\\g &h &3i\\\\\\end{vmatrix}\\\\\n&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d &e &3f\\\\g &h &3i\\\\\\end{vmatrix}=\n(-1)\\cdot3\\begin{vmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\\\\\end{vmatrix}\\\\\n&=-3\\cdot 2=-6\n\\end{align}\\]
\nThe exact sequence of the operations are
\nSo the answer is B.
\n\nProblem 2 Solution
\nThis problem tests the students' knowledge of rank and dimension. Referring to Section 2.9 Dimension and Rank, we know the following important points:
\n\n\n\n
\n- Since the pivot columns of \\(A\\) form a basis for Col \\(A\\), the rank of \\(A\\) is just the number of pivot columns in \\(A\\).
\n- If a matrix \\(A\\) has \\(n\\) columns, then rank \\(A\\) + dim Nul \\(A\\) = \\(n\\).
\n
To find out the number of pivot columns in \\(A\\), we can do elementary row operations to obtain the Row Echelon Form of matrix \\(A\\).
\n\\[\\begin{align}\n&\\begin{bmatrix}1 &2 &2 &5 &0\\\\-2 &0 &-2 &2 &-4\\\\3 &4 &-1 &9 &2\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &4 &-4 &12 &-4\\\\0 &-2 &2 &-6 &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &1 &-1 &3 &-1\\\\0 &1 &-1 &3 &-1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &2 &2 &5 &0\\\\0 &\\color{fuchsia}{1} &-1 &3 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]
\nNow it is clear that this matrix has two pivot columns, thus rank \\(A\\) is 2, and dim Nul \\(A\\) is \\(5-2=3\\).
\nSince \\(5a-3b=5\\times 2-3\\times 3=1\\), the answer is A.
\n\nProblem 3 Solution
\nFor such linear transformation \\(T:\\mathbb R^3\\to\\mathbb R^3\\), onto means for each \\(\\pmb b\\) in the codomain \\(\\mathbb R^{3}\\), there exists at least one solution of \\(T(\\pmb x)=\\pmb b\\).
\nLet's do row reduction first to see
\n\\[\\begin{align}\n&\\begin{bmatrix}1 &t &2\\\\3 &3 &t-5\\\\2 &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\3 &3 &t-5\\\\1 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\1 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &0 &2-\\frac{(t-5)t}{3}\\\\\\end{bmatrix}\n\\end{align}\\]
\nNow inspect the entry of row 3 and column 3, it can be factorized as \\(\\frac{(6-t)(1+t)}{3}\\). If \\(t\\) is 6 or -1, this entry becomes 0. In such cases, for a nonzero \\(b_{3}\\) of \\(\\pmb b\\) in \\(\\mathbb R^{3}\\), there would be no solution at all.
\nSo to make this linear transformation onto \\(\\mathbb R^{3}\\), \\(t\\) cannot be 6 or -1. The answer is E.
\n\nProblem 4 Solution
\nLet's inspect the statements one by one.
\nFor (i), from Section 1.7 Linear Independence, because \\(A\\pmb x=\\pmb 0\\) has only a trivial solution, the columns of the matrix \\(A\\) are linearly independent. So there should be at most one solution for these column vectors to combine and obtain, this statement is true.
\nStatement (ii) is also true. If \\(m<n\\), according to Theorem 8 of Section 1.7, the set of column vectors is linearly dependent, etc a \\(2\\times 3\\) matrix (see Example 5 of Section 1.7). Then \\(A\\pmb x=\\pmb 0\\) has a nontrivial solution. Now referring to Theorem 11 of Section 1.9, this linear transformation of matrix \\(A\\) is NOT one-to-one.
\nThinking of the case \\(3\\times 2\\) for the linear transformation \\(T: \\mathbb R^2\\to\\mathbb R^3\\), we can get one-to-one mapping. But for \\(T: \\mathbb R^3\\to\\mathbb R^2\\), there could be more than 1 point in 3D space mapping to a 2D point. It is not one-to-one.
\nFor (iii), certainly this is not true. A simple example can be a \\(3\\times 2\\) matrix like below \\[\\begin{bmatrix}1 &0\\\\1 &1\\\\0 &1\\\\\\end{bmatrix}\\] The two columns above are NOT linearly dependent.
\nStatement (iv) is true as this is the exact case described by Theorem 4 (c) and (d) in Section 1.4.
\nThe answer is D.
\n\nProblem 5 Solution
\nFrom the given conditions, we know that the columns of \\(A\\) form a linearly dependent set. Equivalently this means \\(A\\) is not invertible and \\(A\\pmb x=\\pmb 0\\) has two nontrivial solutions \\[\\begin{align}\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}5\\\\1\\\\-6\\\\-2\\\\0\\end{bmatrix}=\\pmb 0\\\\\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}0\\\\2\\\\-7\\\\1\\\\3\\end{bmatrix}=\\pmb 0\\\\\n\\end{align}\\] So Statement E is false. Moveover, a noninvertible \\(A\\) has \\(\\det A = 0\\). The statement A is false too.
\nThe two nontrivial solutions for \\(A\\pmb x=\\pmb 0\\) are \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\). As they are also linear independent as one is not a multiple of the other, they should be in the basis for Nul \\(A\\). But we are not sure if there are also other vectors in the basis. We can only deduce that dim Nul \\(A\\) is at least 2. From this, we decide that statement B is false.
\nAgain because rank \\(A\\) + dim Nul \\(A\\) = \\(5\\), and dim Nul \\(A\\) is greater than or equal to 2, rank \\(A\\) must be less than or equal to 3. Statement C is true.
\nStatement D is not true either, since \\([1\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\) is not a linear combination of \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\).
\nSo the answer is C.
\n\nProblem 6 Solution
\nDenote the adjugate of \\(A\\) as \\(B=\\{b_{ij}\\}\\), then \\(b_{ij}=C_{ji}\\), where \\(C_{ji}\\) is the cofactor of \\(A\\). Compute two non-corner entries of \\(B\\) below \\[\\begin{align}\nb_{12}&=C_{21}=(-1)^{2+1}\\begin{vmatrix}0 &-1\\\\1 &-1\\end{vmatrix}=-1\\\\\nb_{21}&=C_{12}=(-1)^{1+2}\\begin{vmatrix}-5 &-1\\\\3 &-1\\end{vmatrix}=-8\n\\end{align}\\]
\nSo the answer is C.
\n\nProblem 7 Solution
\nWe need a set of 4 linearly independent vectors to span \\(\\mathbb R^4\\).
\nAnswer A contains the zero vector, thus the set is not linearly independent.
\nAnswer E contains only 3 vectors, not enough as the basis of \\(\\mathbb R^4\\).
\nAnswer D column 3 is 2 times column 2, and column 5 is equal to column 2 and column 4. So it has only 3 linearly independent vectors. Still not enough
\nAnswer C is also not correct. If we scale 1/3 to column 1, and then add it with columns 2 and 3 altogether, it results in column 4. So only 3 linearly independent vectors.
\nSo the answer is B. Indeed B has 4 linearly independent vectors.
\n\n
Problem 8 Solution
\nThis problem is very similar to Problem 8 of Fall 2022 Midterm I. The solution follows the same steps.
\nReferring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\] We can use this property to find \\(A\\).
\nFirst, denote \\(\\pmb u=\\begin{bmatrix}1\\\\1\\end{bmatrix}\\) and \\(\\pmb v=\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\). It is trivial to see that \\[\\begin{align}\n \\pmb{u}&=1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=\\pmb{e}_1+\\pmb{e}_2\\\\\n \\pmb{v}&=-1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=-\\pmb{e}_1+\\pmb{e}_2\\\\\n \\end{align}\\] This leads to \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}-\\frac{1}{2}\\pmb{v}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}+\\frac{1}{2}\\pmb{v}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=\\frac{1}{2}T(\\pmb{u})-\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=\\frac{1}{2}T(\\pmb{u})+\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}1\\\\1\\end{bmatrix}\n \\end{align}\\]
We know that the standard matrix is \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] as we have \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\) now, the standard matrix \\(A\\) is \\(\\begin{bmatrix}2 &1\\\\3 &1\\end{bmatrix}\\). It is a \\(2\\times 2\\) matrix. The inverse formula is (see Theorem 4 in Section 2.2 The Inverse of A Matrix) \\[\\begin{align}\n A&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\\\\n A^{-1}&=\\frac{1}{ad-bc}\\begin{bmatrix}d &-b\\\\-c &a\\end{bmatrix}\\\\\n \\end{align}\\] This yields \\(A^{-1}=\\begin{bmatrix}-1 &1\\\\3 &-2\\end{bmatrix}\\).
This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\(\\begin{bmatrix}2 &1 &7\\\\3 &1 &9\\end{bmatrix}\\). After row reduction, it becomes \\(\\begin{bmatrix}0 &1 &3\\\\1 &0 &2\\end{bmatrix}\\). This has unique solution \\(\\pmb x=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\).
📝Notes:The students should remeber the inverse formula of \\(2\\times 2\\) matrix!
\n\n
Problem 9 Solution
\nThis problem is also very similar to Problem 9 of Fall 2022 Midterm I. The solution follows the same steps.
\nThe augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &0 &-1 &1\\\\1 &1 &h-1 &3\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &0 &a^2-2h-3 &h-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a^2-2h-3\\).
When \\(h=3\\), the last row entries become all zeros. This system has an infinite number of solutions.
If \\(h=-1\\), last row becomes \\([0\\,0\\,0\\,-4]\\). Now the system is inconsistent and has no solution.
If \\(h\\) is not 3 or -1, last row becomes \\([0\\,0\\,h+1\\,1]\\). We get \\(z=\\frac{1}{h+1}\\). The system has a unique solution.
Problem 10 Solution
\nThis problem is also very similar to Problem 10 of Fall 2022 Midterm I. The solution follows the same steps.
\n\\[\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\1 &0 &5 &13 &20\\\\2 &0 &4 &12 &22\\\\3 &0 &2 &0 &21\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &3 &9 &9\\\\1 &0 &2 &6 &11\\\\0 &0 &-4 &-12 &-12\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &2 &0\\\\0 &0 &1 &3 &3\\end{bmatrix}\n\\] \\[\n\\sim\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &0 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5\\\\0 &0 &\\color{fuchsia}{1} &0 &3\\\\0 &0 &0 &\\color{fuchsia}{1} &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]
\nReferring to Theorem 12 Section 2.8 Matrix Algebra and the Warning message below that (quoted below)
\n\n\nWarning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). Thecolumns of an echelon form \\(B\\) are often not in the column space of \\(A\\).
\n
So the pivot columns of the original matrix \\(A\\) form a basis for the column space of \\(A\\). The basis is the set of columns 1, 3, and 4. \\[\n \\begin{Bmatrix}\\begin{bmatrix}1\\\\1\\\\2\\\\3\\end{bmatrix},\n \\begin{bmatrix}2\\\\5\\\\4\\\\2\\end{bmatrix},\n \\begin{bmatrix}4\\\\13\\\\12\\\\0\\end{bmatrix}\\end{Bmatrix}\n \\]
Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".
\nNow write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5 &0\\\\0 &0 &\\color{fuchsia}{1} &0 &3 &0\\\\0 &0 &0 &\\color{fuchsia}{1} &0 &0\\\\0 &0 &0 &0 &0 &0\\end{bmatrix}\\]
\nThe general solution is \\(x_1=-5x_5\\), \\(x_3=-3x_5\\), \\(x_4=0\\), with \\(x_2\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-5x_5\\\\x_2\\\\-3x_5\\\\0\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]
Here is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \n
---|---|
1 | \nDeterminant and its Properties | \n
2 | \nRank and Dimension of the Null Space of a Matrix, Pivot Columns, Row Reduction Operation | \n
3 | \nLinear Transformation, Onto \\(\\mathbb R^m\\), Linear System Consistency | \n
4 | \nHomogeneous Linear Systems, One-to-One Mapping Linear Transformation, the Column Space of the Matrix | \n
5 | \nLinear Dependency, Invertible Matrix, Determinant, Rank and Dimension of the Null Space of Matrix | \n
6 | \nThe Adjugate of Matrix, The (\\(i,j\\))-cofactor of Matrix | \n
7 | \nLinear Independency, Vector Set Spanning Space \\(\\mathbb R^n\\) | \n
8 | \nLinear Transformation Properties, Standard Matrix for a Linear Transformation | \n
9 | \nRow Echelon Form, Linear System Solution Set and Consistency | \n
10 | \nReduced Row Echelon Form, Basis for the Column Vector Space and the Null Space | \n
As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/","content":"Here comes the solution and analysis for Purdue MA 26500 Spring 2023 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nProblem 1 Solution
\nA For \\(5\\times 7\\) matrix, if \\(rank(A)=5\\), the dimension of the null space is \\(7-5=2\\). So this is wrong.
\nB The matrix has 7 columns, but there are only 5 pivot columns, so the columns of \\(A\\) are NOT linearly independent. It is wrong.
\nC \\(A^T\\) is a \\(7\\times 5\\) matrix, and the rank of \\(A^T\\) is no more than 5. This statement is wrong.
\nD Because there are 5 pivots, each row has one pivot. Thus the rows of \\(A\\) are linearly independent. This statement is TRUE.
\nE From statement D, it can be deduced that the dimension of the row space is 5, not 2.
\nThe answer is D.
\n\nProblem 2 Solution
\nThe vector in this subspace \\(H\\) can be represented as \\[\na\\begin{bmatrix}1\\\\1\\\\0\\\\0\\end{bmatrix}+\nb\\begin{bmatrix}-2\\\\-1\\\\1\\\\0\\end{bmatrix}+\nc\\begin{bmatrix}9\\\\6\\\\-3\\\\0\\end{bmatrix}+\nd\\begin{bmatrix}5\\\\5\\\\1\\\\5\\end{bmatrix}+\ne\\begin{bmatrix}4\\\\-3\\\\-9\\\\-10\\end{bmatrix}\n\\]
\nHere the transformation matrix \\(A\\) has 5 columns and each has 4 entries. Hence these column vectors are not linearly independent.
\n\n\nNote that row operations do not affect the dependence relations between the column vectors. This makes it possible to use row reduction to find a basis for the column space.
\n
\\[\n\\begin{align}\n&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\1 &-1 &6 &5 &-3\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &0 &0 &1 &-2\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &-2 &9 &5 &4\\\\0 &\\color{fuchsia}1 &-3 &0 &-7\\\\0 &0 &0 &\\color{fuchsia}1 &-2\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\end{align}\n\\]
\nThe dimension of \\(H\\) is the number of linearly independent columns of the matrix, which is the number of pivots in \\(A\\)'s row echelon form. So the dimension is 3.
\nThe answer is C.
\n\nProblem 3 Solution
\nFirst, find the eigenvalues for the matrix \\[\n\\begin{align}\n\\det A-\\lambda I &=\\begin{vmatrix}2-\\lambda &2\\\\3 &1-\\lambda\\end{vmatrix}=(\\lambda^2-3\\lambda+2)-6\\\\&=\\lambda^2-3\\lambda-4=(\\lambda+1)(\\lambda-4)=0\n\\end{align}\n\\] The above gives two real eigenvalues \\(-1\\) and \\(4\\). Since they have opposite signs, the origin is a saddle point.
\nThe answer is A.
\n\nProblem 4 Solution
\n(i) is NOT true. Referring to Theorem 4 of Section 5.2 \"The Characteristic Equation\",
\n\n\nIf \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).
\n
But the reverse statement is NOT true. They are matrices that are not similar even though they have the same eigenvalues.
\n(ii) is NOT true either. Referring to Theorem 6 of Section 5.3 \"Diagonalization\",
\n\n\nAn \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n
The book mentions that the above theorem provides a sufficient condition for a matrix to be diagonalizable. So the reverse statement is NOT true. There are examples that a diagonalizable matrix has eigenvalues with multiplicity 2 or more.
\n(iii) Since the identity matrix is symmetric, and \\(\\det A=\\det A^T\\) for \\(n\\times n\\) matrix, we can write \\(\\det (A-\\lambda I) = \\det (A-\\lambda I)^T = \\det(A^T-\\lambda I)\\). So matrix \\(A\\) and its transpose have the same eigenvalues. This statement is TRUE.
\n(iv) This is definitely TRUE as we can find eigenvectors that are linearly independent and span \\(\\mathbb R^n\\).
\n(v) If matrix \\(A\\) has zero eigenvalue, \\(\\det A-0I=\\det A=0\\), it is not invertible. This statement is TRUE.
\nIn summary, statements (iii), (iv), and (v) are TRUE. The answer is E.
\n\nProblem 5 Solution
\nA This vector set does not include zero vector (\\(x = y = 0\\)). So it is not a subspace of \\(V\\).
\nB For eigenvalue 3, we can find out the eigenvector from \\(\\begin{bmatrix}0 &0\\\\2 &0\\end{bmatrix}\\pmb v=\\pmb 0\\), it is \\(\\begin{bmatrix}0\\\\\\ast\\end{bmatrix}\\). All vectors in this set satisfy three subspace properties. So this one is good.
\nC This cannot be the right choice. Since the 3rd entry is always 1, the vector set cannot be closed under vector addition and multiplication by scalars. Also, it does not include zero vector either.
\nD For \\(p(x)=a_0+a_1x+a_2x^2\\) and \\(p(1)p(2)=0\\), this gives \\[(a_0+a_1+a_2)(a_0+2a_1+4a_2)=0\\] To verify if this is closed under vector addition. Define \\(q(x)=b_0+b_1x+b_2x^2\\) that has \\(q(1)q(2)=0\\), this gives \\[(b_0+b_1+b_2)(b_0+2b_1+4b_2)=0\\] Now let \\(r(x)=p(x)+q(x)=c_0+c_1x+c_2x^2\\), where \\(c_i=a_i+b_i\\) for \\(i=0,1,2\\). Is it true that \\[(c_0+c_1+c_2)(c_0+2c_1+4c_2)=0\\] No, it is not necessarily the case. This one is not the right choice either.
\nE Invertible matrix indicates that its determinant is not 0. The all-zero matrix is certainly not invertible, so it is not in the specified set. Moreover, two invertible matrices can add to a non-invertible matrix, such as the following example \\[\n\\begin{bmatrix}2 &1\\\\1 &2\\end{bmatrix}+\\begin{bmatrix}-2 &1\\\\-1 &-2\\end{bmatrix}=\\begin{bmatrix}0 &2\\\\0 &0\\end{bmatrix}\n\\] This set is NOT a subspace of \\(V\\).
\nThe answer is B.
\n\nProblem 6 Solution
\nRecall from the Problem 4 solution that a matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n(i) The following calculation shows this matrix has two eigenvalues 4 and 1. So it is diagonalizable. \\[\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}=(\\lambda^2-5\\lambda+6)-2=(\\lambda-1)(\\lambda-4)=0\\]
\n(ii) It is easy to see that there is one eigenvalue \\(-3\\) with multiplicity 2. However, we can only get one eigenvector \\(\\begin{bmatrix}1\\\\0\\end{bmatrix}\\) for such eigenvalue. So it is NOT diagonalizable.
\n(iii) To find out the eigenvalues for this \\(3\\times 3\\) matrix, do the calculation as below \\[\n\\begin{vmatrix}2-\\lambda &3 &5\\\\0 &2-\\lambda &1\\\\0 &1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)(\\lambda-3)(\\lambda-1)\n\\] So we get 3 eigenvalues 2, 3, and 1. This matrix is diagonalizable.
\n(iv) This is an upper triangular matrix, so the diagonal entries (5, 4, 2) are all eigenvalues. As this matrix has three distinct eigenvalues, it is diagonalizable.
\nSince only (ii) is not diagonalizable, the answer is E.
\n\nProblem 7 Solution
\nThis problem involves complex eigenvalues.
\nStep 1: Find the eigenvalue of the given matrix \\[\n\\begin{vmatrix}1-\\lambda &-1\\\\1 &1-\\lambda\\end{vmatrix}=\\lambda^2-2\\lambda+2=0\n\\] Solve this with the quadratic formula \\[\n\\lambda=\\frac {-b\\pm {\\sqrt {b^{2}-4ac}}}{2a}=\\frac {-(-2)\\pm {\\sqrt {(-2)^2-4\\times 1\\times 2}}}{2\\times 1}=1\\pm i\n\\]
\nStep 2: Find the corresponding eigenvector for \\(\\lambda=1+i\\) \\[\n\\begin{bmatrix}-i &-1\\\\1 &-i\\end{bmatrix}\\sim\\begin{bmatrix}0 &0\\\\1 &-i\\end{bmatrix}\n\\] This gives \\(x_1=ix_2\\), so the eigervector can be \\(\\begin{bmatrix}i\\\\1\\end{bmatrix}\\).
\nStep 3: Generate the real solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\pmb{v}_1 e^{\\lambda_1 t}=e^t(\\cos t+i\\sin t)\\begin{bmatrix}i\\\\1\\end{bmatrix}\\\\\n=e^t\\begin{bmatrix}-\\sin t+i\\cos t\\\\\\cos t+i\\sin t\\end{bmatrix}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}-\\sin t\\\\\\cos t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\cos t\\\\\\sin t\\end{bmatrix}\\]
\nAt first glance, none on the list matches our answer above. However, let's inspect this carefully. We can exclude C and D first since they both have \\(e^{-t}\\) that is not in our answer. Next, it is impossible to be E because it has no minus sign.
\nNow between A and B, which one is most likely to be the right one? We see that B has \\(-\\cos t\\) on top of \\(\\sin t\\). That could not match our answer no matter what \\(c_2\\) is. If we switch \\(c_1\\) and \\(c_2\\) of A and inverse the sign of the 2nd vector, A would become the same as our answer. Since \\(c_1\\) and \\(c_2\\) are just scalars, this deduction is reasonable.
\nSo the answer is A.
\n\n
Problem 8 Solution
\n(1) Directly apply \\(p(t)=t^2-1\\) to the mapping function \\[T(t^2-1)=0^2-1+(1^2-1)t+(2^2-1)t^2=-1+3t^2\\]
\n(2) Denote \\(p(t)=a_0+a_1t+a_2t^2\\), \\(T(p(t))=b_0+b_1t+b_2t^2\\), then \\[\nT(a_0+a_1t+a_2t^2)=a_0+(a_0+a_1+a_2)t+(a_0+2a_1+4a_2)t^2\n\\] So \\[\n\\begin{align}\na_0 &&=b_0\\\\\na_0 &+ a_1 + a_2 &=b_1\\\\\na_0 &+ 2a_1 + 4a_2 &=b_2\n\\end{align}\n\\] This gives the \\([T]_B=\\begin{bmatrix}1 &0 &0\\\\1 &1 &1\\\\1 &2 &4\\end{bmatrix}\\).
\nAlternatively, we can form the same matrix with the transformation of each base vector: \\[\\begin{align}\nT(1)&=1+t+t^2 => \\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}\\\\\nT(t)&=0+t+2t^2 => \\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\\\\nT(t^2)&=0+t+4t^2 => \\begin{bmatrix}0\\\\1\\\\4\\end{bmatrix}\\\n\\end{align}\\]
\n\n
Problem 9 Solution
\n(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}1-\\lambda &2 &-1\\\\0 &3-\\lambda &-1\\\\0 &-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)\\begin{vmatrix}3-\\lambda &-1\\\\-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)(\\lambda-4)(\\lambda-1)\n\\] So there are \\(\\lambda_1=\\lambda_2=1\\), and \\(\\lambda_3=4\\).
\nNext is to find the eigenvectors for each eigenvalue
\nFor \\(\\lambda_1=\\lambda_2=1\\), apply row reduction to the agumented matrix of the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) \\[\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &2 &-1 &0\\\\0 &-2 &1 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &0 &0 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With two free variables \\(x_1\\) and \\(x_2\\), we get \\(x_3=2x_2\\). So the parametric vector form can be written as \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\nx_1\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}+x_2\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\n\\] So the eigenvectors are \\(\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\\) and \\(\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\).
For \\(\\lambda_3=4\\), follow the same process \\[\n\\begin{bmatrix}-3 &2 &-1 &0\\\\0 &-1 &-1 &0\\\\0 &-2 &-2 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}3 &-2 &1 &0\\\\0 &1 &1 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With one free variable \\(x_3\\), we get \\(x_1=x_2=-x_3\\). So the eigenvector can be written as \\(\\begin{bmatrix}1\\\\1\\\\-1\\end{bmatrix}\\) (or \\(\\begin{bmatrix}-1\\\\-1\\\\1\\end{bmatrix}\\)).
(2) We can directly construct \\(P\\) from the vectors in last step, and construct \\(D\\) from the corresponding eigenvalues. Here are the answers: \\[\nP=\\begin{bmatrix}\\color{fuchsia}1 &\\color{fuchsia}0 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}1 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}2 &\\color{blue}{-1}\\end{bmatrix},\\;\nD=\\begin{bmatrix}\\color{fuchsia}1 &0 &0\\\\0 &\\color{fuchsia}1 &0\\\\0 &0 &\\color{blue}4\\end{bmatrix}\n\\]
\n\n
Problem 10 Solution
\n(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\)
\n\\[\\begin{vmatrix}-4-\\lambda &-5\\\\2 &3-\\lambda\\end{vmatrix}=(\\lambda^2+\\lambda-12)+10=(\\lambda+2)(\\lambda-1)=0\\] So there are two eigervalues \\(-2\\) and 1. Next is to find the eigenvectors for each eigenvalue.
For \\(\\lambda=-2\\), the matrix becomes \\[\\begin{bmatrix}-2 &-5\\\\2 &5\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\2 &5\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}5\\\\-2\\end{bmatrix}\\).
\nFor \\(\\lambda=1\\), the matrix becomes \\[\\begin{bmatrix}-5 &-5\\\\2 &2\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\1 &1\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).
\n(2) The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}5\\\\-2\\end{bmatrix}e^{-2t}+c_2\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^t\n\\]
\n(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n5c_1+c_2&=-3\\\\\n-2c_1-c_2&=0\n\\end{align}\\] This gives \\(c_1=-1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=-5e^{-2}+2e^1+2e^{-2}-2e^{-1}=-3e^{-2}\\).
\n\nHere are the key knowledge points covered by this exam:
\nRSA encryption algorithm is one of the core technologies of modern public-key cryptography and is widely used on the Internet. As a classical algorithm of public-key cryptography, the programming implementation of textbook RSA can help us quickly grasp its mathematical mechanism and design ideas, and accumulate important experience in the software implementation of cryptography. Here is a detailed example of textbook RSA implementation in Python 3.8 programming environment.
\nRandom numbers should not be generated with a method chosen at random.
— Donald Knuth(American computer scientist, mathematician, and professor emeritus at Stanford University, the 1974 recipient of the ACM Turing Award, often called the \"father of the analysis of algorithms\")
The security of the RSA encryption algorithm is built on the mathematical challenge of factoring the product of two large prime numbers. The first step in constructing the RSA encryption system is to generate two large prime numbers \\(p\\) and \\(q\\), and calculate the modulus \\(N=pq\\). \\(N\\) is the length of the RSA key, the larger the more secure. Nowadays, practical systems require the key length to be no less than 2048 bits, with corresponding \\(p\\) and \\(q\\) about 1024 bits each.
\nA general effectiveness method for generating such large random prime numbers is a probability-based randomization algorithm, which proceeds as follows:
\nIn this software implementation, the first step can generate odd numbers directly. Also for demonstration purposes, the second step uses the first 50 prime numbers greater than 2 for the basic primality test. The whole process is shown in the following flowchart.
\nFor the first step, Python function programming requires importing the library function randrange()
from the random
library. The function uses the input number of bits n in the exponents of 2, which specify the start and end values of randrange()
. It also sets the step size to 2 to ensure that only n-bit random odd values are returned.
from random import randrange |
The code for the second step is simple. It defines an array with elements of 50 prime numbers after 2, then uses a double loop in the function to implement the basic primality test. The inner for
loop runs the test with the elements of the prime array one by one. It aborts back to the outer loop immediately upon failure, from there it calls the function in the first step to generate the next candidate odd number and test again.
def get_lowlevel_prime(n): |
The Miller-Rabin primality test1 in the third step is a widely used method for testing prime numbers. It uses a probabilistic algorithm to determine whether a given number is a composite or possibly a prime number. Although also based on Fermat's little theorem, the Miller-Rabin primality test is much more efficient than the Fermat primality test. Before showing the Python implementation of the Miller-Rabin prime test, a brief description of how it works is given here.
\nBy Fermat's little theorem, for a prime \\(n\\), if the integer \\(a\\) is not a multiple of \\(n\\), then we have \\(a^{n-1}\\equiv 1\\pmod n\\). Therefore if \\(n>2\\), \\(n-1\\) is an even number and must be expressed in the form \\(2^{s}*d\\), where both \\(s\\) and \\(d\\) are positive integers and \\(d\\) is odd. This yields \\[a^{2^{s}*d}\\equiv 1\\pmod n\\] If we keep taking the square root of the left side of the above equation and then modulo it, we will always get \\(1\\) or \\(-1\\)2. If we get \\(1\\), it means that the following equation ② holds; if we never get \\(1\\), then equation ① holds: \\[a^{d}\\equiv 1{\\pmod {n}}{\\text{ ①}}\\] \\[a^{2^{r}d}\\equiv -1{\\pmod {n}}{\\text{ ②}}\\] where \\(r\\) is some integer that lies in the interval \\([0, s-1]\\). So, if \\(n\\) is a prime number greater than \\(2\\), there must be either ① or ② that holds. The conditional statement of this law is also true, i.e.** if we can find a \\(\\pmb{a}\\) such that for any \\(\\pmb{0\\leq r\\leq s-1}\\) the following two equations are satisfied: \\[\\pmb{a^{d}\\not \\equiv 1\\pmod n}\\] \\[\\pmb{a^{2^{r}d}\\not \\equiv -1\\pmod n}\\] Then \\(\\pmb{n}\\) must not be a prime number**. This is the mathematical concept of the Miller-Rabin primality test. For the number \\(n\\) to be tested, after calculating the values of \\(s\\) and \\(d\\), the base \\(a\\) is chosen randomly and the above two equations are tested iteratively. If neither holds, \\(n\\) is a composite number, otherwise, \\(n\\) may be a prime number. Repeating this process, the probability of \\(n\\) being a true prime gets larger and larger. Calculations show that after \\(k\\) rounds of testing, the maximum error rate of the Miller-Rabin primality test does not exceed \\(4^{-k}\\).
\nThe Miller-Rabin primality test function implemented in Python is as follows, with the variables n,s,d,k
in the code corresponding to the above description.
def miller_rabin_primality_check(n, k=20): |
Putting all of the above together, the whole process can be wrapped into the following function, where the input of the function is the number of bits and the output is a presumed random large prime number.
\ndef get_random_prime(num_bits): |
Greatest Common Divisor (GCD) gcd(a,b)
and Least Common Multiple lcm(a,b)
:
\nThe RSA encryption algorithm needs to calculate the Carmichael function \\(\\lambda(N)\\) of modulus \\(N\\), with the formula \\(\\lambda(pq)= \\operatorname{lcm}(p - 1, q - 1)\\), where the least common multiple function is used. The relationship between the least common multiple and the greatest common divisor is: \\[\\operatorname{lcm}(a,b)={\\frac{(a\\cdot b)}{\\gcd(a,b)}}\\] There is an efficient Euclidean algorithm for finding the greatest common divisor, which is based on the principle that the greatest common divisor of two integers is equal to the greatest common divisor of the smaller number and the remainder of the division of the two numbers. The specific implementation of Euclid's algorithm can be done iteratively or recursively. The iterative implementation of the maximum convention function is applied here, and the Python code for the two functions is as follows:
def gcd(a, b):
'''Computes the Great Common Divisor using the Euclid's algorithm'''
while b:
a, b = b, a % b
return a
def lcm(a, b):
"""Computes the Lowest Common Multiple using the GCD method."""
return a // gcd(a, b) * b
Extended Euclidean Algorithm exgcd(a,b)
and Modular Multiplicative Inverse invmod(e,m)
:
\nThe RSA key pair satisfies the equation \\((d⋅e)\\bmod \\lambda(N)=1\\), i.e., the two are mutually modular multiplicative inverses with respect to the modulus \\(\\lambda(N)\\). The extended Euclidean algorithm can be applied to solve the modular multiplicative inverse \\(d\\) of the public key exponent \\(e\\) quickly. The principle of the algorithm is that given integers \\(a,b\\), it is possible to find integers \\(x,y\\) (one of which is likely to be negative) while finding the greatest common divisor of \\(a,b\\) such that they satisfy Bézout's identity: \\[a⋅x+b⋅y=\\gcd(a, b)\\] substituted into the parameters \\(a=e\\) and \\(b=m=\\lambda(N)\\) of the RSA encryption algorithm, and since \\(e\\) and \\(\\lambda(N)\\) are coprime, we can get: \\[e⋅x+m⋅y=1\\] the solved \\(x\\) is the modulo multiplicative inverse \\(d\\) of \\(e\\). The Python implementations of these two functions are given below:
def exgcd(a, b):
"""Extended Euclidean Algorithm that can give back all gcd, s, t
such that they can make Bézout's identity: gcd(a,b) = a*s + b*t
Return: (gcd, s, t) as tuple"""
old_s, s = 1, 0
old_t, t = 0, 1
while b:
q = a // b
s, old_s = old_s - q * s, s
t, old_t = old_t - q * t, t
a, b = b, a % b
return a, old_s, old_t
def invmod(e, m):
"""Find out the modular multiplicative inverse x of the input integer
e with respect to the modulus m. Return the minimum positive x"""
g, x, y = exgcd(e, m)
assert g == 1
# Now we have e*x + m*y = g = 1, so e*x ≡ 1 (mod m).
# The modular multiplicative inverse of e is x.
if x < 0:
x += m
return x
Note: Textbook RSA has inherent security vulnerabilities. The reference implementation in the Python language given here is for learning and demonstration purposes only, by no means to be used in actual applications. Otherwise, it may cause serious information security incidents. Keep this in mind!
\nBased on the object-oriented programming idea, it can be designed to encapsulate the RSA keys and all corresponding operations into a Python class. The decryption and signature generation of the RSA class are each implemented in two ways, regular and fast. The fast method is based on the Chinese Remainder Theorem and Fermat's Little Theorem. The following describes the implementation details of the RSA class.
\nObject Initialization Method
\nInitialization method __init__()
has the user-defined paramaters with default values shown as below:
This method internally calls the get_random_prime()
function to generate two large random prime numbers \\(p\\) and \\(q\\) that are about half the bit-length of the key. It then calculates their Carmichael function and verifies that the result and \\(e\\) are coprime. If not, it repeats the process till found. Thereafter it computes the modulus \\(N\\) and uses the modular multiplicative inverse function invmod()
to determine the private exponent \\(d\\). If a fast decryption or signature generation function is required, three additional values are computed as follows: \\[\\begin{align}\nd_P&=d\\bmod (p-1)\\\\\nd_Q&=d\\bmod (q-1)\\\\\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\n\\end{align}\\]
RSA_DEFAULT_EXPONENT = 65537
RSA_DEFAULT_MODULUS_LEN = 2048
class RSA:
"""Implements the RSA public key encryption/decryption with default
exponent 65537 and default key size 2048"""
def __init__(self, key_length=RSA_DEFAULT_MODULUS_LEN,
exponent=RSA_DEFAULT_EXPONENT, fast_decrypt=False):
self.e = exponent
self.fast = fast_decrypt
t = 0
p = q = 2
while gcd(self.e, t) != 1:
p = get_random_prime(key_length // 2)
q = get_random_prime(key_length // 2)
t = lcm(p - 1, q - 1)
self.n = p * q
self.d = invmod(self.e, t)
if (fast_decrypt):
self.p, self.q = p, q
self.d_P = self.d % (p - 1)
self.d_Q = self.d % (q - 1)
self.q_Inv = invmod(q, p)
Encryption and Decryption Methods
\nRSA encryption and regular decryption formulas are \\[\\begin{align}\nc\\equiv m^e\\pmod N\\\\\nm\\equiv c^d\\pmod N\n\\end{align}\\] Python built-in pow()
function supports modular exponentiation. The above two can be achieved by simply doing the corresponding integer to byte sequence conversions and then calling pow() with the public or private key exponent:
def encrypt(self, binary_data: bytes):
int_data = uint_from_bytes(binary_data)
return pow(int_data, self.e, self.n)
\t
def decrypt(self, encrypted_int_data: int):
int_data = pow(encrypted_int_data, self.d, self.n)
return uint_to_bytes(int_data)
def decrypt_fast(self, encrypted_int_data: int):
# Use Chinese Remaider Theorem + Fermat's Little Theorem to
# do fast RSA description
assert self.fast == True
m1 = pow(encrypted_int_data, self.d_P, self.p)
m2 = pow(encrypted_int_data, self.d_Q, self.q)
t = m1 - m2
if t < 0:
t += self.p
h = (self.q_Inv * t) % self.p
m = (m2 + h * self.q) % self.n
return uint_to_bytes(m)
Signature Generation and Verification Methods
\nThe RSA digital signature generation and verification methods are very similar to encryption and regular decryption functions, except that the public and private exponents are used in reverse. The signature generation uses the private exponent, while the verification method uses the public key exponent. The implementation of fast signature generation is the same as the fast decryption steps, but the input and output data are converted and adjusted accordingly. The specific implementations are presented below:
def generate_signature(self, encoded_msg_digest: bytes):
"""Use RSA private key to generate Digital Signature for given
encoded message digest"""
int_data = uint_from_bytes(encoded_msg_digest)
return pow(int_data, self.d, self.n)
\t
def generate_signature_fast(self, encoded_msg_digest: bytes):
# Use Chinese Remaider Theorem + Fermat's Little Theorem to
# do fast RSA signature generation
assert self.fast == True
int_data = uint_from_bytes(encoded_msg_digest)
s1 = pow(int_data, self.d_P, self.p)
s2 = pow(int_data, self.d_Q, self.q)
t = s1 - s2
if t < 0:
t += self.p
h = (self.q_Inv * t) % self.p
s = (s2 + h * self.q) % self.n
return s
def verify_signature(self, digital_signature: int):
"""Use RSA public key to decrypt given Digital Signature"""
int_data = pow(digital_signature, self.e, self.n)
return uint_to_bytes(int_data)
Once the RSA class is completed, it is ready for testing. To test the basic encryption and decryption functions, first initialize an RSA object with the following parameters
\nNext, we can call the encryption method encrypt()
of the RSA object instance to encrypt the input message, and then feed the ciphertext to the decryption method decrypt()
and the fast decryption method decrypt_fast()
respectively. We use the assert
statement to compare the result with the original message. The code snippet is as follows.
# ---- Test RSA class ---- |
Likewise, we can also test the signature methods. In this case, we need to add the following import
statement to the beginning of the file
from hashlib import sha1 |
This allows us to generate the message digest with the library function sha1()
and then call the generate_signature()
and generate_signature_fast()
methods of the RSA object instance to generate the signature, respectively. Both signatures are fed to the verify_signature()` function and the result should be consistent with the original message digest. This test code is shown below.
mdg = sha1(msg).digest() |
If no AssertionError
is seen, we would get the following output, indicating that both the encryption and signature tests passed.
RSA message encryption/decryption test passes! |
Once the functional tests are passed, it is time to see how the performance of fast decryption is. We are interested in what speedup ratio we can achieve, which requires timing the execution of the code. For time measurements in Python programming, we have to import the functions urandom()
and timeit()
from the Python built-in libraries os
and timeit
, respectively:
from os import urandom |
urandom()
is for generaring random bype sequence, while timeit()
can time the execution of a given code segment. For the sake of convenience, the RSA decryption methods to be timed are first packed into two functions:
decrypt_norm()
- Regular decryption methoddecrypt_fast()
- Fast descryption methodBoth use the assert
statement to check the result, as shown in the code below:
def decrypt_norm(tester, ctxt: bytes, msg: bytes): |
The time code sets up two nested for
loops:
The outer loop iterates over different key lengths klen
, from 512 bits to 4096 bits in 5 levels, and the corresponding RSA object obj
is initialized with:
klen
The variable rpt
is also set in the outer loop to be the square root of the key length, and the timing variables t_n
and t_f
are cleared to zeros.
The inner layer also loops 5 times, each time executing the following operations:
\nurandom()
to generate a random sequence of bytes mg
with bits half the length of the keyobj.encrypt()
to generate the ciphertext ct
timeit()
and enter the packing functions decrypt_norm()
and decrypt_fast()
with the decryption-related parameters obj
, ct
and mg
, respectively, and set the number of executions to rpt
timeit()
function are stored cumulatively in t_n
and t_f
At the end of each inner loop, the current key length, the mean value of the timing statistics, and the calculated speedup ratio t_n/t_f
are printed. The actual program segment is printed below:
print("Start RSA fast decryption profiling...") |
Here are the results on a Macbook Pro laptop:
\nStart RSA fast decryption profiling... |
The test results confirm the effectiveness of the fast decryption method. As the key length increases, the computational intensity gradually increases and the running timeshare of the core decryption operation becomes more prominent, so the speedup ratio grows correspondingly. However, the final speedup ratio tends to a stable value of about 3.5, which is consistent with the upper bound of the theoretical estimate (\\(4-\\varepsilon\\)).
\nThe Python code implementation of the textbook RSA helps reinforce the basic number theory knowledge we have learned and also benefits us with an in-depth understanding of the RSA encryption algorithm. On this basis, we can also extend to experiment some RSA elementary attack and defense techniques to further master this key technology of public-key cryptography. For the complete program click here to download: textbook-rsa.py.gz
\nGary Lee Miller, a professor of computer science at Carnegie Mellon University, first proposed a deterministic algorithm based on the unproven generalized Riemann hypothesis. Later Professor Michael O. Rabin of the Hebrew University of Jerusalem, Israel, modified it to obtain an unconditional probabilistic algorithm.↩︎
This is because it follows from \\(x^2\\equiv 1\\pmod n\\) that \\((x-1)(x+1)=x^{2}-1\\equiv 0\\pmod n\\). Since \\(n\\) is a prime number, by Euclid's Lemma, it must divide either \\(x- 1\\) or \\(x+1\\), so \\(x\\bmod n\\) must be \\(1\\) or \\(-1\\).↩︎
Network Attached Storage (NAS) provides data access to a heterogeneous group of clients over computer networks. As hard drive prices continue to drop, NAS devices have made their way into the homes of the masses. Leading brands in the SMB and home NAS market, such as Synology, have their products range in price from as low as ﹩300 to ﹩700 for the high models. But if you are a Raspberry Pi player, you can build a very nice home NAS and streaming service for only about half the cost of the lowest price.
\nKnowledge obtained on the papers always feels shallow, must know this thing to practice.
— LU You (Chinese historian and poet of the Southern Song Dynasty)
This blog records the whole process of building a Raspberry Pi NAS and home media server, including project planning, system implementation, and performance review. It also covers some important experiences and lessons that could hopefully benefit anyone interested in this DIY project.
\nRaspberry Pi 4B features an upgraded 1.8GHz Broadcom BCM2711(quad-core Cortex-A72)processor and onboard RAM up to 8GB. It includes two new USB 3.0 ports and a full-speed Gigabit Ethernet interface. The power supply is also updated to a USB-C connector. All these greatly improve system throughput and overall comprehensive performance, and we can use them to create a full-featured home NAS.
For NAS system software, OpenMediaVault (OMV) is a complete NAS solution based on Debian Linux. It is a Linux rewrite of the well-known free and open-source NAS server system FreeNAS (based on FreeBSD). The salient features of OMV are
\nWhile primarily designed for home environments or small home offices, OMV's use is not limited to those scenarios. The system is built on a modular design. It can be easily extended with available plugins right after the installation of the base system. OMV is the NAS server system software we are looking for.
\nThe NAS system with media playback services provides an excellent audio/video-on-demand experience in a home network environment. Plex Media Server software integrates Internet media services (YouTube, Vimeo, TED, etc.) and local multimedia libraries to provide streaming media playback on users' various devices. The features of Plex for managing local libraries are
\nThe Plex Media Server software itself is free and supports a wide range of operating systems, making it ideal for integration with home NAS.
\nThese cover all the software needed for our NAS project, but they are not enough for a complete NAS system. We also need a preferred case, otherwise, the Raspberry Pi NAS will only run bare metal. Although there are many cases available in the market for Raspberry Pi 4B, as a NAS system we need a case kit that can accommodate at least 1-2 internal SSD/HDD and must also have a good heat dissipation design.
\nAfter some review and comparison, we chose Geekworm's NASPi Raspberry Pi 4B NAS storage kit. NASPi is a NUC (Next Unit of Computing) style NAS storage kit designed for the latest Raspberry Pi 4B. It consists of three components:
\nAll these components are packed into a case made of aluminum alloy with an anodized surface.
\nThereon our NAS project can be planned with the following subsystems:
\nIt is important to note that NAS servers are generally headless systems without a keyboard, mouse, or monitor. This poses some challenges for the installation, configuration, and tuning of hardware and software systems. In practice, as described in the next section, we run an SSH terminal connection to complete the basic project implementation process.
\nThe execution of this project was divided into four stages, which are described in detail as follows.
\nIn the first stage, we need to prepare the Raspberry Pi OS and do some basic unit tests. This is important, if we delay the OS test until the entire NSAPi kit is assembled, it will be troublesome to find problems with the Raspberry Pi then.
\nFirst, insert the microSD card into the USB adapter and connect it to the macOS computer, then go to the Raspberry Pi website and download the Raspberry Pi Imager software to run. From the application screen, click CHOOSE OS > Raspberry Pi OS (other) > Raspberry Pi OS Lite (32-bit) step by step. This selects the lightweight Raspberry Pi OS that does not require a desktop environment, and then click CHOOSE STORAGE to pick the microSD card.
\nNext is a trick - hit the ctrl-shift-x
key combination and the following advanced options dialog box will pop up Here is exactly the option we need to enable SSH on boot up - Enable SSH. It also allows the user to pre-set a password for the default username
pi
(default is raspberry). Once set up, click SAVE to return to the main page and then click WRITE to start formatting the microSD card and writing OS to it. When finished, remove the microSD card and insert it into the Raspberry Pi, connect the Ethernet cable then power it up.
At this point we encountered a problem: since the installed system does not have a desktop environment, it cannot connect to the keyboard, mouse, and monitor, so how do we find its IP address? There are two ways:
\nThe log of the Nmap tool run can be seen below. Notice that a new IP address 192.168.2.4 is showing up in the scan report. Rerunning Nmap against this address alone, we saw that TCP port 22 was open. We could roughly determine that this might be our newly online Raspberry Pi:
\n❯ nmap -sn 192.168.2.0/24 |
Next, try SSH connection
\n❯ ssh pi@192.168.2.4 |
Once confirmed, we executed the following commands in the Raspberry Pi to update and upgrade the system:
\npi@raspberrypi:~ $ sudo apt update && sudo apt upgrade |
This stage concluded with the stability test of the Raspberry Pi 4B system Ethernet connection. The test was executed on a macOS computer using the simple ping command, setting the -i 0.05
option to specify 20 packets per second and the -t 3600
option for one hour run
❯ sudo ping -i 0.05 192.168.2.4 -t 3600 |
There should be no more than 1% packet loss or timeout on a subnet with no wireless connectivity, otherwise, it should be checked for troubleshooting. As a matter of fact, in our test, it was happening that nearly 10% of ping packets got lost and the SSH connection dropped intermittently. Searching the Internet, we found that there have been quite a few reports of similar issues with the Raspberry Pi 4B Ethernet connection. The analysis and suggestions given by people on the relevant forums focus on the following
\nPractically, we tried all of the above with little success. Later, we found that the home router connected to the Raspberry Pi 4B was a Belkin N750 DB made in 2011. Although it provides Wi-Fi dual-band 802.11n and 4 Gigabit Ethernet ports, the manufacturing date is too long ago, which makes people doubt its interoperability. Also points 2 and 3 of the above report are essentially interoperability issues. Thinking of these, we immediately ordered the TP-Link TL-SG105 5-port Gigabit Ethernet switch. After receiving it, we extended the Gigabit Ethernet port of N750 with TL-SG105, connected Raspberry Pi 4B to TL-SG105, and retested it. Sure enough, this time the ping packet loss rate was less than 0.1% and the SSH connection became solid.
\nThe conclusion is that the Raspberry Pi 4B Gigabit Ethernet interface may have compatibility issues with some older devices, which can be solved by inserting a desktop switch with good interoperability between the two.
\nIn the second stage, we assembled the NSAPi storage kit, intending to finish all hardware installation and complete the standalone NAS body.
\nThe NSAPi supports either an internal SSD or HDD. The project picked a Samsung 870 EVO 500GB internal SSD, here we ought to first make sure the SSD works properly on its own, otherwise, we would have to disassemble the NASPi to replace it. The SSD can be hooked up to Windows for file systems and basic read/write operation checks. In the case of a newly purchased SSD, the following steps can be done on Windows to quickly format it:
\n⚠️Note: Here the chosen file system is NTFS. OMV supports NTFS mounting and reads/writes.
\nBefore the actual hardware assembly, a special software provided by Geekworm - PWM fan control script - must be installed. PWM fan speed adjustment to temperature change is a major feature that lets NASPi stand out from other hardware solutions. So this step is critical.
\nReferring to Geekworm's X-C1 software wiki page, the installation command sequence on the SSH session connected to the Raspberry Pi 4B system is as follows
\nsudo apt-get install -y git pigpio |
If you can't do git clone
directly on Raspberry Pi 4B, you can first download the X-C1 software on the SSH client, then transfer it to Raspberry Pi 4B using scp. After that, continue to execute the subsequent commands
❯ scp -r x-c1 pi@192.168.2.4:/home/pi/ |
How does X-C1 software control PWM fan?
\nThe core of X-C1 software is a Python script named fan.py, which is presented below
\n#!/usr/bin/python |
Its logic is quite simple. With the pigpio module imported, it first initializes a PWM control object and then starts a while loop with a 1-second sleep cycle inside. The CPU temperature is read at each cycle, and the duty cycle of PWM is set according to the temperature level to control the fan speed. The duty cycle is 0 when it is lower than 30℃, and the fan stops; when it is higher than 75℃, the duty cycle is 100, and the fan spins at full speed. Users can modify the temperature threshold and duty cycle parameters in the program to customize the PWM fan control.
\n\nIn addition, the following pi-temp.sh script, which reads out the GPU and CPU temperatures, is also useful
\npi@raspberrypi:~ $ cat ./pi-temp.sh |
Below is a snapshot of the Geekworm NASPi parts out of the box (except for the Raspberry Pi 4B on the far right of the second row and the screwdriver in the lower right corner)
\n The three key components in the second row, from left to right, are
The assembly process was done step-by-step mainly by referring to NASPi installation video on Youtube, and the steps are generalized as follows.
\nNow the installation of the internal accessories has been completed, we have a view of this
\nAt this point, we added the USB-C power and pressed the front button to start the system, we could see the PWM fan started to spin. It was also observed that the fan spin rate was not constant, which demonstrated that the temperature controller PWM fan was working properly.
\nThe front button switch with embedded blue LED decides the whole system's on/off state and can be tested below
\nRunning the off
command on the SSH connection can also trigger a safe shutdown. Be cautious that we should not use the Linux shutdown
command, as that would not power down the X-C1 board.
After the button switch test, we now unplugged the USB 3.0 connector and inserted the entire module into the case. Next was to add the back panel and tighten the screws, then re-insert the USB 3.0 connector. This completed the whole NASPi storage kit assembly process. Below are the front and rear views of the final system provided by Geekworm (all interfaces and vents are marked).
\nThe third stage is for installing and configuring the key software package of the NAS system - PMV. The goal is to bring up the basic network file access service. Before restarting the NAS, we plugged a Seagate 2TB external HDD into the remaining USB 3.0 port. After booting, connected SSH to NASPi from macOS and performed the following process.
\nInstalling OMV is as simple as running the following command line directly from a terminal with an SSH connection.
\nwget -O - https://raw.githubusercontent.com/OpenMediaVault-Plugin-Developers/installScript/master/install | sudo bash |
Due to the large size of the entire OMV package, this installation process can take a long time. After the installation, the IP address of the system may change and you will need to reconnect to SSH at this time.
\n(Reading database ... 51781 files and directories currently installed.) |
After reconnecting, you can use dpkg
to view the OMV packages. As you can see, the latest version of OMV installed here is 6.0.5.
pi@raspberrypi:~ $ dpkg -l | grep openme |
At this point OMV's workbench is live. Launching a browser on a macOS computer and typing in the IP address will open the beautiful login screen (click on the 🌍 icon in the upper right corner to select the user interface language): After logging in with the default username and password shown above, you will see the Workbench screen. The first thing you should do at this point is to click the ⚙️ icon in the top right corner to bring up the settings menu and click \"Change Password\". You can also change the language here
Clicking on \"Dashboard\" in the settings menu allows you to select the relevant components to be enabled. The menu on the left side provides task navigation for administrators and can be hidden when not needed. The complete OMV administration manual can be found in the online documentation
Next is the key process for configuring the NAS, which consists of the following 5 steps.
\nScan for mounted disk drives
\nClick Storage > Disks from the sidebar menu to enter the hard drive management page. If there is an external USB storage device just plugged in, you can click 🔍 here to scan it out. The scan results for this system are as follows. The internal Samsung 500GB SSD and external Seagate 2TB HDD are detected, and the 32GB microSD that contains the entire software system is listed at the top:
On the SSH terminal, we could see the information for the same set of mounted drivers
\npi@raspberrypi:~ $ df -h | grep disk
/dev/sdb2 466G 13G 454G 3% /srv/dev-disk-by-uuid-D0604B68604B547E
/dev/sda1 1.9T 131G 1.7T 7% /srv/dev-disk-by-uuid-DEB2474FB2472B7B
Mount disk drive file systems
\nClick Storage > File Systems from the sidebar menu to enter the file system management page. If the storage device does not have a file system yet, click ⨁ to Create or Mount the file system. OMV can create/mount ext4, ext3, JFS, and xfs file systems, but only mounts are supported for the NTFS file system. The following figure shows that OMV correctly mounts NTFS file systems for SSDs and HDDs:
Set Shared Folders
\nFrom the sidebar menu, click Storage > File Systems to access the shared folder management page. Here, click ⨁ to create a shared folder. When creating it, specify the name, corresponding file system, and relative path, and you can also add comments. Select the created folder and click the pencil icon again to edit the related information. This system sets the relative paths of shared folders Zixi-Primary and Zixi-Secondary for SSD and HDD respectively Notice the orange alert at the top of the figure above, which alerts the administrator that the configurations have changed and must click on the ✔️ icon to take effect.
Add shared folder access users
\nClick User Management > Users from the sidebar menu to enter the user management page. The system's default user pi has root privileges and cannot be used for file-sharing access due to security concerns. So you need to add a new user separately. On this page, click ⨁ to Create or Import user, only user name and password are required when creating a new user, others are optional. Once created, select this user and click the third folder+key icon (prompting \"Shared folder privileges\") to enter the following privileges settings page As shown in the figure, for this new user zixi, the administrator can set the read and write access permissions for each shared folder.
Start file share services
\nIf you expand the \"Services\" item in the navigation menu, you can see that OMV manages five services: FTP, NFS, Rsync, SMB/CIFS, and SSH. SSH is enabled at the beginning of the system OS image preparation. NFS and SMB/CIFS are the most common network file-sharing protocols, and both are supported by macOS. Take SMB/CIFS as an example here. Click Services > SMB/CIFS from the sidebar menu to enter the management page. The page contains two buttons: Settings and Shares. Click \"Settings\" first to activate the SMB/CIFS service and configure the workgroup name on the new page, other options can be left as default. After saving, it returns to the SMB/CIFS administration page. Then enter \"Shares\", click ⨁ to Create shared folders Zixi-Primary and Zixi-Secondary on the new page then save. After that, click the ✔️ icon in the orange warning bar to make all configuration updates take effect, and you will end up with the following result
Now our Raspberry Pi NAS system is ready for file sharing and the SMB/CIFS service is started. After checking the relevant components to turn on, our dashboard live monitoring looks like this
Once the server side is ready, we need to add the network share folder on the client side as follows.
\nOnce the client side is set up, users can perform various operations on the network share folder as if it were a local directory, such as previewing, creating new, opening or copying files, creating new subdirectories, or deleting existing subdirectories.
\nThe last stage is to install and configure the Plex Media Server, and then start a network streaming service.
\nThe process of installing Plex Media Server requires HTTPS transport support, so we must first install the https-transport package. SSH to our Raspberry Pi NAS and execute the install command
\nsudo apt-get install apt-transport-https |
Next add the Plex repository to the system, which requires downloading the Plex sign key first. Here are the related commands and run logs
\npi@raspberrypi:~ $ curl https://downloads.plex.tv/plex-keys/PlexSign.key | sudo apt-key add - |
Use the same apt-key
command to check the newly added Plex sign key
pi@raspberrypi:~ $ apt-key list |
You can see that Plex uses 4096-bit RSA keys. For the warning message \"apt-key is deprecated...\" in the above log, you can ignore it for now. Go to read some discussion on the askubuntu forum if you are interested.
\nThe next step is to add the Plex repository to the system repository list, and then update the packages echo deb https://downloads.plex.tv/repo/deb public main | sudo tee /etc/apt/sources.list.d/plexmediaserver.list
sudo apt-get update
pi@raspberrypi:~ $ sudo apt install plexmediaserver |
The log shows a question is asked about the Plex media server list (plexmediaserver.list), just choose the default N. When we see \"Installation successful\", we know that the installation was successful. At this point, the Plex streaming service is up and running. Invoking the Nmap scan again from the macOS side, we find that TCP port 32400 for Plex service is open.
\n❯ nmap -p1-65535 192.168.2.4 | grep open |
The configuration of the Plex Media Server has been done on the web GUI. Launch a browser on the macOS computer and type in the URL http://<IP-address>:32400/web, now we can see the following page if no surprise We can sign in with a Google, Facebook, or Apple account, or we can enter an email to create a new account. Follow the instructions on the page step by step, no need for any payment, soon we reach the Server Setup page. Here we can configure the server name and add libraries. Normally we don't need to access our home media server from outside, so remember to uncheck the \"Allow me to access my media outside my home\" box in this step. To add a library, first select the type of library (movies, TV episodes, music, photos, etc.), then click the \"BROWSE FOR MEDIA FOLDER\" button to browse and select the corresponding folder. Once the library is added, the included media files will immediately appear in the local service directory, as shown in the screenshot below
Here we have a local server named ZIXI-RPI-NAS for our Raspberry Pi NAS, the movie directory in the library shows The Matrix trilogy and is playing the first one The Matrix. Move your mouse over the server name and ➕ icon will appear to the right, click on it to continue adding new media libraries.
Once the Plex Media Server is configured, we can open a browser from any device on our home network to do streaming on-demand, without the need to download additional applications. The whole experience is just like our own proprietary home Netflix service. This is awesome!
\nBy connecting a macOS laptop to one of the remaining ports of the TL-SG105, we could perform some simple same-subnet tests to evaluate the performance of this NAS system fully.
\nReferring to Geekworm NASPi Stress Test Wiki page, we executed the following command over SSH connection, which cloned the test script from GitHub and ran the stress test:
\ngit clone https://github.com/geekworm-com/rpi-cpu-stress |
Simultaneously we established a second SSH session and ran htop
to monitor system status. The screenshot below was taken while close to the 5-minute mark (left is the htop real-time display, and right is the stress test output) Dividing the
temp
value on the right side by 1000 gave the CPU temperature. All 4 CPU cores reached 100% full load during the test, while the maximum temperature did not exceed 70°C. At this moment, there was no obvious heat sensation when touching the case. Typing ctrl-c
to stop the stress test, and then executing the temperature measurement script again
pi@raspberrypi:~ $ ./pi-temp.sh |
The system temperature returned to a low range value. This test result assures the system meets the design goal.
\nThe file transfer speed can be roughly measured with the secure remote copy tool SCP. First, create a 1GB size file by running the mkfile
command on the macOS client, then copy it to the user directory of the remote NAS system with the scp
command
❯ mkfile 1G test-nas.dmg |
After the copy was done, it would print the time spent and the deduced speed. Running the command with the source and the destination reversed would give us the speed of receiving a file from the NAS system.
\n❯ scp pi@192.168.2.4:/home/pi/test-nas.dmg test-nas-rx.dmg |
Repeated 3 times and got the results listed below
\nTransfor Type | \nServer Operation | \nTime (s) | \nSpeed (MB/s) | \n
---|---|---|---|
Send | \nWrite | \n53 | \n19.2 | \n
Send | \nWrite | \n45 | \n22.5 | \n
Send | \nWrite | \n50 | \n20.4 | \n
Receive | \nRead | \n15 | \n65.7 | \n
Receive | \nRead | \n16 | \n60.3 | \n
Receive | \nRead | \n15 | \n66.3 | \n
As can be seen, the speed of remote write is around 20MB/s, while the speed of remote file read can reach over 60MB/s. Considering that scp-related encryption and decryption are implemented in software on general-purpose Raspberry Pi systems, this result should be considered passable.
\nThe real test of the NAS's performance is the network drive read/write speed test. For this, we downloaded the AmorphousDiskMark app from Apple's App Store. This is an easy and efficient drive speed test that measures the read/write performance of a storage device in terms of MB/s and IOPS (input/output operations per second). It has four types of tests:
\nThe above queue depths are the default values, but other values are also available. In addition, users can also modify the test file size and duration.
\nRun the application on the macOS client and select the remote SMB folders Zixi-Primary (Samsung SSD) and Zixi-Secondary (Seagate HDD) respectively at the top, then click the All
button in the upper left corner to start the NAS drive speed test process. A side-by-side comparison of the two test results is shown below
This gives a few observations:
\nThese are not surprising and are consistent with the test results on macOS laptops with direct external SSDs and HDDs, only with the lower numbers. With this NAS system, both the SSD and HDD are connected via the USB 3.0 interface. USB 3.0 supports transfer speeds of up to 5Gbit/s, so the performance bottleneck of the system is the network interface bandwidth and processor power.
\nThat being said, for both SSDs and HDDs, the transfer speeds have been more than 900Mbit/s at 1MB sequential read and queue depth 8, close to the upper bandwidth limit of the Gigabit Ethernet interface. This read speed can support a single 1080p60 video stream at a frame rate of 60fps or 2 parallel 1080i50 video streams at a frame rate of 25fps, which is sufficient for home streaming services. In another media service test, the NAS system performs satisfactorily with three computers playing HD video on demand and one phone playing MP3 music without any lag.
\nThis completes our Raspberry Pi home NAS project. Now we can move our NAS to a more permanent location to provide network file and streaming services for the whole family.
\nEconomically, our home NAS has the cost summarized in the table below (excluding SSD/HDD)
\nDevices | \nFunctions | \nCost($) | \n
---|---|---|
Raspberry Pi 4B 2/4/8GB RAM | \nPrimary hardware system | \n45/55/75 | \n
Samsung 32GB EVO+ Class-10 Micro SDHC | \nOS storage | \n10 | \n
Geekworm NASPi Raspberry Pi 4B NAS Storage Kit | \nCase, extending board and PWM fan | \n60 | \n
Geekworm 20W 5V 4A USB-C Power Adaptor | \nPower supply | \n15 | \n
TP-Link TL-SG105 5-Port Gigabit Ethernet Switch | \nDesktop switch | \n15 | \n
Even with the choice of 8GB RAM Raspberry Pi 4B, the whole cost is only $175, a little more than half of the price of the low-end brand NAS sold in the market. Unless there are a lot of client devices that need streaming services, the memory consumption is usually under 2GB, so the 2GB Raspberry Pi 4B should be able to work in most home scenarios. That cuts the cost down to $145, less than half the MSRP.
\nOn the other hand, this DIY project was a very good exercise of hands-on practice, helping us gain valuable intuitive experience in building network connections, configuring system hardware and software, and tuning and testing application layer services. To sum up, the home NAS system built with Raspberry Pi 4B and OMV, combined with a Plex media server, provides a cost-effective solution for file backup and streaming media services in the home network.
\nAppendix: List of related devices and Amazon links
\n\n\n","categories":["DIY Projects"],"tags":["Raspberry Pi","NAS"]},{"title":"RSA: Attack and Defense (II)","url":"/en/2023/11/17/RSA-attack-defense-2/","content":"Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\nCanaKit Raspberry Pi 4B 8GB RAM + 128GB MicroSD Extrem Kit https://amzn.to/3DUeDfm
\n
\nSamsung 32GB EVO+ Class 10 Micro SDHC with Adapter https://amzn.to/3FLkTb7
\nGeekworm NASPi 2.5\" SATA HDD/SSD Raspberry Pi 4B NAS Storage Kit https://amzn.to/3m5djAi
\nGeekworm Raspberry Pi 4 20W 5V 4A USB-C Power Adaptor https://amzn.to/3m1EXOf
\nTP-Link TL-SG105 5-Port Gigabit Ethernet Switch https://amzn.to/3pRkBsi
\nSamsung 870 EVO 500GB 2.5\" SATA III Internal SSD https://amzn.to/3DPKnCl
\nSeagate Portable 2TB USB 3.0 External HDD https://amzn.to/3EYegl4
\nSynology 2-Bay 2GB NAS DiskStation DS220+ https://amzn.to/3Jp5qjd
\nSynology 5-Bay 8GB NAS DiskStation DS1520+ https://amzn.to/3qniQDm
This article first supplements two specific integer factorization methods - Fermat's factorization method and Pollard's rho algorithm, explaining the essence of their algorithms and applicable scenarios, and provides a Python reference implementation. Next, it analyzes in detail a classic low private exponent attack - Wiener's attack, elaborating on the mathematical basis, the attack principle, and the attack procedure, with a complete Python program. The article also cites the latest research paper proposing a new upper bound for the private exponent when Wiener's attack is successful and verifies the correctness of this limit with a test case.
\nThe enemy knows the system being used.
— Claude Shannon (American mathematician, electrical engineer, computer scientist, and cryptographer known as the \"father of information theory\".)
Previous article: RSA: Attack and Defense (I)
\nEven if the RSA modulus \\(N\\) is a very big number (with sufficient bits), problems can still arise if the gap between the prime factors \\(p\\) and \\(q\\) is too small or too large. In such cases, there are specific factorization algorithms that can effectively retrieve p and q from the public modulus N.
\nWhen the prime factors \\(p\\) and \\(q\\) are very close, Fermat's factorization method can factorize the modulus N in a very short time. Fermat's factorization method is named after the French mathematician Pierre de Fermat. Its base point is that every odd integer can be represented as the difference between two squares, i.e. \\[N=a^2-b^2\\] Applying algebraic factorization on the right side yields \\((a+b)(a-b)\\). If neither factor is one, it is a nontrivial factor of \\(N\\). For the RSA modulus \\(N\\), assuming \\(p>q\\), correspondingly \\(p=a+b\\) and \\(q=a-b\\). In turn, it can be deduced that \\[N=\\left({\\frac {p+q}{2}}\\right)^{2}-\\left({\\frac {p-q}{2}}\\right)^{2}\\] The idea of Fermat's factorization method is to start from \\(\\lceil{\\sqrt N}\\rceil\\) and try successive values of a, then verify if \\(a^{2}-N=b^{2}\\). If it is true, the two nontrivial factors \\(p\\) and \\(q\\) are found. The number of steps required by this method is approximately \\[{\\frac{p+q}{2}}-{\\sqrt N}=\\frac{({\\sqrt p}-{\\sqrt q})^{2}}{2}=\\frac{({\\sqrt N}-q)^{2}}{2q}\\] In general, Fermat's factorization method is not much better than trial division. In the worst case, it may be slower. However, when the difference between \\(p\\) and \\(q\\) is not large, and \\(q\\) is very close to \\(\\sqrt N\\), the number of steps becomes very small. In the extreme case, if the difference between \\(q\\) and \\(\\sqrt N\\) is less than \\({\\left(4N\\right)}^{\\frac 1 4}\\), this method only takes one step to finish.
\nBelow is a Python implementation of Fermat's factorization method, and an example of applying it to factorize the RSA modulus N:
\nimport gmpy2 |
The FermatFactor()
function defined at the beginning of the program implements the Fermat factorization method. It calls three library functions of gmpy2: isqrt()
to find the square root of an integer, square()
to execute the squaring operation, and is_square()
to verify if the input is a square number. Two large prime numbers \\(p\\) and \\(q\\) of 154 decimal digits each are defined later, and multiplying them gives \\(N\\). Then \\(N\\) is fed into the FermatFactor()
function and the program starts timing. When the function returns, it prints the elapsed time and confirms the factorization.
N = 55089599753625499150129246679078411260946554356961748980861372828434789664694269460953507615455541204658984798121874916511031276020889949113155608279765385693784204971246654484161179832345357692487854383961212865469152326807704510472371156179457167612793412416133943976901478047318514990960333355366785001217 |
As can be seen, in less than half a minute, this large number of 308 decimal digits (about 1024 bits) was successfully factorized! Going back and examining \\(p\\) and \\(q\\), one can see that the first 71 digits of these two large prime numbers of 154 decimal digits are exactly the same. This is exactly the scenario in which the Fermat factorization method exerts its power. If you simply modify the FermatFactor()
function to save the starting \\(a\\) value and compare it to the value at the end of the loop, you get a loop count of 60613989. With such a small number value, it's no wonder that the factorization is done so quickly.
Therefore, the choice of the large prime numbers \\(p\\) and \\(q\\) must not only be random but also be far enough apart. After obtaining two large prime numbers, the difference between them shall be checked. If it is too small, regeneration is required to prevent attackers from using Fermat's factorization method to crack it.
\nOn the opposite end, if the gap between the large prime factors \\(p\\) and \\(q\\) is too large, they may be cracked by Pollard's rho algorithm. This algorithm was invented by British mathematician John Pollard1 in 1975. It requires only a small amount of storage space, and its expected running time is proportional to the square root of the smallest prime factor of the composite number being factorized.
\nThe core idea of Pollard's rho algorithm is to use the collision pattern of traversal sequences to search for factors, and its stochastic and recursive nature allows it to factorize integers efficiently in relatively low complexity. First, for \\(N=pq\\), assume that \\(p\\) is the smaller nontrivial factor. The algorithm defines a polynomial modulo \\(N\\) \\[f(x)=(x^{2}+c){\\pmod N}\\] A pseudorandom sequence can be generated by making recursive calls with this polynomial, and the sequence generation formula is \\(x_{n+1}=f(x_n)\\). For example, given an initial value of \\(x_0=2\\) and a constant \\(c=1\\), it follows that \\[\\begin{align}\nx_1&=f(2)=5\\\\\nx_2&=f(x_1)=f(f(2))=26\\\\\nx_3&=f(x_2)=f(f(f(2)))=677\\\\\n\\end{align}\\] For two numbers \\(x_i\\) and \\(x_j\\) in the generated sequence, \\(|x_i-x_j|\\) must be a multiple of \\(p\\) if \\(x_i\\neq x_j\\) and \\(x_i\\equiv x_j{\\pmod p}\\). In this case, calculating \\(\\gcd(|x_i-x_j|,N)\\) results in \\(p\\). Based on the Birthday Paradox, in the worst case, it is expected that after generating about \\(\\sqrt p\\) numbers, there will be two numbers that are the same under the modulus \\(p\\), thus successfully factorizing \\(N\\). However, the time complexity of performing pairwise comparisons is still unsatisfactory. In addition, storing so many numbers is also troublesome when N is large.
\nHow to solve these problems? This is where the ingenuity of Pollard's rho algorithm lies. Pollard found that the sequence generated by this pseudorandom number generator has two properties:
\nInsightful of these two properties, Pollard utilizes Floyd's cycle-finding algorithm (also known as the tortoise and hare algorithm) to set up the fast and slow nodes \\(x_h\\) and \\(x_t\\). Starting from the same initial value \\(x_0\\), the slow node \\(x_t\\) moves to the next node in the sequence every step, while the fast node \\(x_h\\) moves forward by two nodes at a time, i.e. \\[\\begin{align}\nx_t&=f(x_t)\\\\\nx_h&=f(f(x_h))\\\\\n\\end{align}\\] After that, calculate \\(\\gcd(|x_h-x_t|,N)\\), and the result that is greater than 1 and less than \\(N\\) is \\(p\\), otherwise continue with the same steps. With this design, since each move is equivalent to checking a new node spacing, pairwise comparisons are unnecessary. If not found, eventually the fast and slow nodes will meet on the cycle, at which time the result of finding the greatest common divisor is \\(N\\). The algorithm's recommendation at this point is to exit and regenerate the pseudorandom number sequence with a different initial value or constant \\(c\\) and try again.
\nThis is the classic Pollard's rho algorithm. Its time complexity is \\(𝑂(\\sqrt p\\log N)\\) (\\(\\log\\) comes from the required \\(\\gcd\\) operations). For RSA modulus \\(N\\), obviously \\(p\\leq \\sqrt N\\), so the upper bound on the time complexity can be written as \\(𝑂(N^{\\frac 1 4}\\log N)\\). The time complexity expression for Pollard's rho algorithm indicates that the smaller the minimum prime factor of the composite number being factorized, the faster the factorization is expected to be. An excessively small \\(p\\) is extremely unsafe.
\nProgramming Pollard's rho algorithm is not difficult. The following Python code shows a function implementation of the algorithm, PollardRhoFactor()
, and some test cases
import gmpy2 |
The function PollardRhoFactor()
accepts three arguments: n
is the composite number to be factorized, seed
is the initial value of the pseudorandom sequence, and c
is the constant value in the generating polynomial. The function internally uses two while
to form a double loop: inside the outer loop defines the generating polynomial f
and the fast and slow nodes h
and t
, while the node moving steps and the greatest common divisor operation are implemented in the inner loop. The inner loop ends only if the greatest common divisor d
is not 1. At this point, if d
is not equal to n
, the function returns the non-trivial factor d
. Otherwise, d
equals n
, meaning the fast and slow nodes have met on the cycle. In this situation, the code in the outer loop resets seed
to the value of the fast node and increments c
, thus restarting a new round of search.
Running the above code on a MacBook Pro (2019), the output is as follows
\nN P Elapsed Time (s) |
This result proves the effectiveness of Pollard's rho algorithm. In particular, for the last test, the input to the function was the Fermat number \\(F_8\\) (defined as \\(F_{n}=2^{2^{n}}+1\\), where \\(n\\) is a non-negative integer). In 1980, Pollard and Australian mathematician Richard Brent 2 working together applied this algorithm to factorize \\(F_8\\) for the first time. The factorization took 2 hours on a UNIVAC 1100/42 computer. And now, on a commercial off-the-shelf laptop computer, Pollard's rho algorithm revealed the smaller prime factor 1238926361552897 of \\(F_8\\) in 64.4 seconds.
\nSubsequently, Pollard and Brent made further improvements to the algorithm. They observed that if \\(\\gcd(d, N)>1\\), for any positive integer \\(k\\), there is also \\(\\gcd(kd, N)>1\\). So multiplying \\(k\\) consecutive \\((|x_h-x_t| \\pmod N)\\) and taking the modulo \\(N\\) with the product, and then solving for the greatest common divisor with \\(N\\) should obtain the same result. This method replaces \\(k\\) times \\(\\gcd\\) with \\((k-1)\\) times multiplications modulo \\(N\\) and a single \\(\\gcd\\), thus achieving acceleration. The downside is that occasionally it may cause the algorithm to fail by introducing a repeated factor. When this happens, it then suffices to reset \\(k\\) to 1 and fall back to the regular Pollard's rho algorithm.
\nThe following Python function implements the improved Pollard's rho algorithm. It adds an extra for
loop to implement the multiplication of \\(k\\) consecutive differences modulo \\(N\\), with the resulting product stored in the variable mult
. mult
is fed to the greatest common divisor function with \\(N\\), and the result is assigned to d
for further check. If this fails, \\(k\\) is set to 1 in the outer loop.
def PollardRhoFactor2(n, seed, c, k): |
Using the same test case, called with \\(k\\) set to 100, the program runs as follows
\nN P Elapsed Time (s) |
It can be seen that for relatively small composite \\(N\\), the improvement is not significant. As \\(N\\) becomes larger, the speedup is noticeable. For the 78-bit decimal Fermat number \\(F_8\\), the improved Pollard's rho algorithm takes only 46.6 seconds, which is a speedup of more than 27% over the regular algorithm. The improved Pollard \\(\\rho\\) algorithm indeed brings significant speedup.
\nTo summarize the above analysis, implementation, and testing of Pollard's rho algorithm, it is necessary to set a numerical lower bound for the generated prime numbers \\(p\\) and \\(q\\) to be used by RSA. If either of them is too small, it must be regenerated or it may be cracked by an attacker applying Pollard's rho algorithm.
\nFor some particular application scenarios (e.g., smart cards and IoT), limited by the computational capability and low-power requirements of the device, a smaller value of private exponent \\(d\\) is favored for fast decryption or digital signing. However, a very low private exponent is very dangerous, and there are some clever attacks that can totally breach such an RSA cryptosystem.
\nIn 1990, Canadian cryptographer Michael J. Wiener conceived an attack scheme3 based on continued fraction approximation that can effectively recover the private exponent \\(d\\) from the RSA public key \\((N, e)\\) under certain conditions. Before explaining how this attack works, it is important to briefly introduce the concept and key properties of continued fraction.
\nThe continuous fraction itself is just a mathematical expression, but it introduces a new perspective on the study of real numbers. The following is a typical continued fraction \\[x = a_0 + \\cfrac{1}{a_1 + \\cfrac{1}{a_2 + \\cfrac{1}{\\ddots\\,}}}\\] where \\(a_{0}\\) is an integer and all other \\(a_{i}(i=1,\\ldots ,n)\\) are positive integers. One can abbreviate the continued fraction as \\(x=[a_0;a_1,a_2,\\ldots,a_n]\\). Continued fractions have the following properties:
\nEvery rational number can be expressed as a finite continued fraction, i.e., a finite number of \\(a_{i}\\). Every rational number has an essentially unique simple continued fraction representation with infinite terms. Here are two examples: \\[\\begin{align}\n\\frac {68} {75}&=0+\\cfrac {1} {1+\\cfrac {1} {\\small 9+\\cfrac {1} {\\scriptsize 1+\\cfrac {1} {2+\\cfrac {1} {2}}}}}=[0;1,9,1,2,2]\\\\\nπ&=[3;7,15,1,292,1,1,1,2,…]\n\\end{align}\\]
To calculate the continued fraction representation of a positive rational number \\(f\\), first subtract the integer part of \\(f\\), then find the reciprocal of the difference and repeat till the difference is zero. Let \\(a_i\\) be the integer quotient, \\(r_i\\) be the difference of the \\(i\\)th step, and \\(n\\) be the number of steps, then \\[\\begin{align}\na_0 &= \\lfloor f \\rfloor, &r_0 &= f - a_0\\\\\na_i&={\\large\\lfloor} \\frac 1 {r_{i-1}} {\\large\\rfloor}, &r_i &=\\frac 1 {r_{i-1}} - a_i \\quad (i = 1, 2, ..., n)\\\\\n\\end{align}\\] The corresponding Python function implementing the continued fraction expansion of rationals is as follows
\ndef cf_expansion(nm: int, dn:int) -> list:
""" Continued Fraction Expansion of Rationals
Parameters:
nm - nominator
dn - denomainator
Return:
List for the abbreviated notation of the continued fraction
"""
cf = []
a, r = nm // dn, nm % dn
cf.append(a)
while r != 0:
nm, dn = dn, r
a = nm // dn
r = nm % dn
cf.append(a)
return cf
For both rational and irrational numbers, the initial segments of their continued fraction representations produce increasingly accurate rational approximations. These rational numbers are called the convergents of the continued fraction. The even convergents continually increase, but are always less than the original number; while the odd ones continually decrease, but are always greater than the original number. Denote the numerator and denominator of the \\(i\\)-th convergent as \\(h_i\\) and \\(k_i\\) respectively, and define \\(h_{-1}=1,h_{-2}=0\\) and \\(k_{-1}=0,k_{-2}=1\\), then the recursive formula for calculating the convergents is \\[\\begin{align}\n\\frac {h_0} {k_0} &= [0] = \\frac 0 1 = 0<\\frac {68} {75}\\\\\n\\frac {h_1} {k_1} &= [0;1] = \\frac 1 1 = 1>\\frac {68} {75}\\\\\n\\frac {h_2} {k_2} &= [0;1,9] = \\frac 9 {10}<\\frac {68} {75}\\\\\n\\frac {h_3} {k_3} &= [0;1,9,1] = \\frac {10} {11}>\\frac {68} {75}\\\\\n\\frac {h_4} {k_4} &= [0;1,9,1,2] = \\frac {29} {32}<\\frac {68} {75}\\\\\n\\end{align}\\] It can be verified that these convergents satisfy the aforementioned property and are getting closer to the true value. The following Python function implements a convergent generator for a given concatenated fraction expansion, and it returns a tuple of objects consisting of the convergent's numerator and denominator.
\ndef cf_convergent(cf: list) -> (int, int):
""" Calculates the convergents of a continued fraction
Parameters:
cf - list for the continued fraction expansion
Return:
A generator object of the convergent tuple
(numerator, denominator)
"""
nm = [] # Numerator
dn = [] # Denominators
for i in range(len(cf)):
if i == 0:
ni, di = cf[i], 1
elif i == 1:
ni, di = cf[i]*cf[i-1] + 1, cf[i]
else: # i > 1
ni = cf[i]*nm[i-1] + nm[i-2]
di = cf[i]*dn[i-1] + dn[i-2]
nm.append(ni)
dn.append(di)
yield ni, di
Regarding the convergents of continued fractions, there is also an important Legendre4 theorem: Let \\(a∈ \\mathbb Z, b ∈ \\mathbb Z^+\\) such that \\[\\left\\lvert\\,f - \\frac a b\\right\\rvert< \\frac 1 {2b^2}\\] then \\(\\frac a b\\) is a convergent of the continued fraction of \\(f\\).
Now analyze how Wiener's attack works. From the relationship between RSA public and private exponent \\(ed\\equiv 1 {\\pmod {\\varphi(N)}}\\), it can be deduced that there exists an integer \\(k\\) such that \\[ed - k\\varphi(N) = 1\\] Dividing both sides by \\(d\\varphi(N)\\) gives \\[\\left\\lvert\\frac e {\\varphi(N)} - \\frac k d\\right\\rvert = \\frac 1 {d{\\varphi(N)}}\\] Careful observation of this formula reveals that because \\(\\varphi(N)\\) itself is very large, and \\(\\gcd(k,d)=1\\), \\(\\frac k d\\) is very close to \\(\\frac e {\\varphi(N)}\\). In addition, \\[\\varphi(N)=(p-1)(q-1)=N-(p+q)+1\\] Its difference from \\(N\\) is also relatively small. Therefore, \\(\\frac k d\\) and \\(\\frac e N\\) also do not differ by much. Since RSA's \\((N,e)\\) are public, Wiener boldly conceived - if \\(\\pmb{\\frac e N}\\) is expanded into a continued fraction, it is possible that \\(\\pmb{\\frac k d}\\) is one of its convergents!
\nSo how to verify if a certain convergent is indeed \\(\\frac k d\\)? With \\(k\\) and \\(d\\), \\(\\varphi (N)\\) can be calculated, thereby obtaining \\((p+q)\\). Since both \\((p+q)\\) and \\(pq\\) are known, constructing a simple quadratic equation5 can solve for \\(p\\) and \\(q\\). If their product equals \\(N\\), then \\(k\\) and \\(d\\) are correct and the attack succeeds.
\nWhat are the conditions for Wiener's attack to work? Referring to Legendre's theorem mentioned above, it can be deduced that if \\[\\left\\lvert\\frac e N - \\frac k d\\right\\rvert < \\frac 1 {2{d^2}}\\] then \\(\\frac k d\\) must be a convergent of \\(\\frac e N\\). This formula can also be used to derive an upper bound of the private exponent d for a feasible attack. Wiener's original paper states the upper bound as \\(N^{\\frac 1 4}\\), but without detailed analysis. In 1999, American cryptographer Dan Boneh6 provided the first rigorous proof of the upper bound, showing that under the constraints \\(q<p<2q\\) and \\(e<\\varphi(N)\\), Wiener's attack applies for \\(d<\\frac 1 3 N^{\\frac 1 4}\\). In a new paper published in 2019, several researchers at the University of Wollongong in Australia further expanded the upper bound under the same constraints to \\[d\\leq \\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4=\\frac 1 {2.06...}N^\\frac 1 4\\]
\nNote that for simplicity, the above analysis of Wiener's attack mechanism is based on the Euler phi function \\(\\varphi (N)\\). In reality, RSA key pairs are often generated using the Carmichael function \\(\\lambda(N)\\). The relationship between the two is: \\[\\varphi (N)=\\lambda(n)\\cdot\\gcd(p-1,q-1)\\] It can be proven that starting from \\(ed≡1{\\pmod{\\lambda(N)}}\\), the same conclusions can be reached. Interested readers may refer to Wiener's original paper for details.
\nWith an understanding of the mechanism of Wiener's attack, the attack workflow can be summarized as follows:
\nThe complete Python implementation is as follows:
\nimport gmpy2 |
The code above ends with two test cases. Referring to the program output below, the first test case gives a small RSA modulus \\(N\\) and a relatively large \\(e\\), which is precisely the scenario where Wiener's attack comes into play. The program calls the attack function wiener_attack() that quickly returns \\(d\\) as 7, then decrypts a ciphertext and recovers the original plaintext \"Wiener's attack success!\".
\nThe second test case sets a 2048-bit \\(N\\) and \\(e\\), and Wiener's attack also succeeds swiftly. The program also verifies that the cracked \\(d\\) (511 bits) is greater than the old bound old_b
(\\(N^{\\frac 1 4}\\)), but slightly less than the new bound new_b
(\\(\\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4\\)). This confirms the conclusion of the University of Wollongong researchers.
p = 105192975360365123391387526351896101933106732127903638948310435293844052701259 |
These two test cases prove the effectiveness and prerequisites of Wiener's attack. To prevent Wiener's attack, the RSA private exponent \\(d\\) must be greater than the upper bound. Choosing \\(d\\) no less than \\(N^{\\frac 1 2}\\) is a more prudent scheme. In practice, the optimized decryption using Fermat's theorem and Chinese remainder theorem is often used, so that even larger \\(d\\) can achieve fast decryption and digital signing.
\n\n\nTo be continued, stay tuned for the next article: RSA: Attack and Defense (III)
\n
John Pollard, a British mathematician, the recipient of 1999 RSA Award for Excellence in Mathematics for major contributions to algebraic cryptanalysis of integer factorization and discrete logarithm.↩︎
Richard Peirce Brent, an Australian mathematician and computer scientist, an emeritus professor at the Australian National University.↩︎
M. Wiener, “Cryptanalysis of short RSA secret exponents,” IEEE Trans. Inform. Theory, vol. 36, pp. 553–558, May 1990↩︎
Adrien-Marie Legendre (1752-1833), a French mathematician who made numerous contributions to mathematics.↩︎
Dan Boneh, an Israeli–American professor in applied cryptography and computer security at Stanford University, a member of the National Academy of Engineering.↩︎
RSA is a public-key cryptosystem built on top of an asymmetric encryption algorithm, which was jointly invented by three cryptographers and computer scientists at the Massachusetts Institute of Technology in 1977. The RSA public-key encryption algorithm and cryptosystem provide data confidentiality and signature verification functions widely used on the Internet. Since its birth, RSA has become a major research object of modern cryptography. Many cryptanalysts and information security experts have been studying its possible theoretical flaws and technical loopholes to ensure security and reliability in practical applications.
\nThere are certain things whose number is unknown. If we count them by threes, we have two left over; by fives, we have three left over; and by sevens, two are left over. How many things are there?
— Sunzi Suanjing, Volume 2.26
Fortunately, after more than 40 years of extensive research and practical application tests, although many sophisticated attack methods have been discovered, RSA is generally safe. These attack methods all take advantage of the improper use of RSA or the vulnerability of software and hardware implementations, and cannot shake the security foundation of its encryption algorithm. On the other hand, the research on these attack methods shows that implementing a safe and robust RSA application is not a simple task. A consensus in cryptography and network security hardware and software engineering practice is: never roll your own cryptography!1 The appropriate solution is to use an existing, well-tested, and reliably maintained library or API to implement the RSA algorithm and protocol application.
\nHere is a brief survey of the common means of attacking RSA, the mathematical mechanism on which the attack is based, and the corresponding protective measures. Referring to the previous article, let’s start by reviewing the working mechanism and process of RSA:
\nNote that the second and third steps in the original RSA paper use Euler's totient function \\(\\varphi(N)\\) instead of \\(\\lambda(N)\\). The relationship between these two functions is: \\[\\varphi(N)=\\lambda(N)⋅\\operatorname{gcd}(p-1,q-1)\\] Here \\(\\operatorname{gcd}\\) is the greatest common divisor function. Using \\(\\lambda(N)\\) can yield the minimum workable private exponent \\(d\\), which is conducive to efficient decryption and signature operations. Implementations that follow the above procedure, whether using Euler's or Carmichael's functions, are often referred to as \"textbook RSA \".
\nTextbook RSA is insecure, and there are many simple and effective means of attack. Before discussing the security holes of the textbook RSA in detail, it is necessary to review the first known attack method - integer factorization!
\nThe theoretical cornerstone of the security of the RSA encryption algorithm is the problem of factoring large numbers. If we can separate \\(p\\) and \\(q\\) from the known \\(N\\), we can immediately derive the private exponent \\(d\\) and thus completely crack RSA. Factoring large numbers is a presumed difficult computational problem. The best-known asymptotic running time algorithm is General Number Field Sieve, and its time complexity is \\({\\displaystyle \\exp \\left(\\left(c+o(1)\\right)(\\ln N)^{\\frac {1}{3}}(\\ln \\ln N)^{\\frac {2}{3}}\\right)}\\), where the constant \\(c = 4/\\sqrt[3]{9}\\),\\(\\displaystyle \\exp\\) and \\(\\displaystyle \\exp\\) is the exponential function of Euler's number (2.718).
\nFor a given large number, it is difficult to accurately estimate the actual complexity of applying the GNFS algorithm. However, based on the heuristic complexity empirical estimation, we can roughly see the increasing trend of computational time complexity:
\nThe rapid development of computer software and hardware technology has made many tasks that seemed impossible in the past become a reality. Check the latest record released by the RSA Factoring Challenge website. In February 2020, a team led by French computational mathematician Paul Zimmermann successfully decomposed the large number RSA-250 with 250 decimal digits (829 bits):
\nRSA-250 = 6413528947707158027879019017057738908482501474294344720811685963202453234463 |
announcement
\nAccording to the announcement of the factorization released by Zimmerman, using a 2.1GHz Intel Xeon Gold 6130 processor, the total computing time to complete this task is about 2700 CPU core-years. This number may seem large, but in today's era of cluster computing, grid computing, and cloud computing for the masses, it's not a stretch to think that organizations with strong financial backing can reduce computing time to hours or even minutes. As an example, go to the online tool website of the free open-source mathematical software system SageMath and enter the following first 5 lines of Sage Python code:
\np=random_prime(2**120) |
The result was obtained within minutes, and a large number of 72 decimal digits (240 bits) was decomposed. You know, in the 1977 RSA paper, it is mentioned that it takes about 104 days to decompose a 75-digit decimal number. The technological progress of mankind is so amazing!
\nAs the attacker's spear becomes sharper and sharper, the defender's shield must become thicker and thicker. Therefore, 1024-bit RSA is no longer secure, and applications should not use public key \\(N\\) values that are less than 2048 bits. And when high security is required, choose 4096-bit RSA.
\nAlthough the decomposition of large numbers is an attack method known to everyone, the security vulnerabilities caused by some low-level errors commonly found in RSA applications make it possible to use simple attacks to succeed, and some typical ones are explained below.
\nIn the early development of RSA, finding large prime numbers took quite a bit of time based on the backward computing power of the time. Therefore, some system implementations tried to share the modulus \\(N\\). The idea was to generate only one set \\((p,q)\\), and then all users would use the same \\(N=pq\\) values, with a central authority that everyone trusted assigning key pairs \\((e_i,d_i)\\) to each user \\(i\\), and nothing would go wrong as long as the respective private keys \\(d_i\\) were kept. Unfortunately, this is a catastrophic mistake! This implementation has two huge security holes:
\nThe user \\(i\\) can decompose \\(N\\) using his own key pair \\((e_i,d_i)\\). Whether \\(d\\) is generated using the Euler function \\(\\varphi(N)\\) or the Carmichael function \\(\\lambda(N)\\), there are algorithms that quickly derive the prime factors \\(p\\) and \\(q\\) from a given \\(d\\) 2. And once \\(p\\) and \\(q\\) are known, user \\(i\\) can compute any other user's private key \\(d_j\\) with one's public key \\((N,e_j)\\). At this point, the other users have no secrets from user \\(i\\).
Even if all users do not have the knowledge and skill to decompose \\(N\\), or are \"nice\" enough not to know the other users' private keys, a hacker can still perform a common modulus attack to break the users' messages. If the public keys of two users, Alice and Bob, are \\(e_1\\) and \\(e_2\\), and \\(e_1\\) and \\(e_2\\) happen to be mutually prime (which is very likely), then by Bézout's identity, the eavesdropper Eve can find that \\(s\\) and \\(t\\) satisfy: \\[e_{1}s+e_{2}t=gcd(e_1,e_2)=1\\] At this point, if someone sends the same message \\(m\\) to Alice and Bob, Eve can decrypt \\(m\\) after recording the two ciphertexts \\(c_1\\) and \\(c_2\\) and performing the following operation: \\[c_1^s⋅c_2^t\\equiv(m^{e _1})^s⋅(m^{e_2})^t\\equiv m^{e_{1}s+e_{2}t}\\equiv m\\pmod N\\] The corresponding Python function code is shown below.
\ndef common_modulus(e1, e2, N, c1, c2):
# Call the extended Euclidean algorithm function
g, s, t = gymp2.gcdext(e1, e2)
assert g == 1
if s < 0:
# Find c1's modular multiplicative inverse\t\t re = int(gmpy2.invert(c1, N))
c1 = pow(re, s*(-1), N)
c2 = pow(c2, t, N)
else:
# t is negative, find c2's modular multiplicative inverse
re = int(gmpy2.invert(c2, N))
c2 = pow(re, t*(-1), N)
c1 = pow(c1, a, N)
return (c1*c2) % N
Is it possible to reuse only \\(p\\) or \\(q\\) since the shared modulus \\(N\\) is proven to be insecure? This seems to avoid the common-modulus attack and ensure that each user's public key \\(N\\) value is unique. Big mistake! This is an even worse idea! The attacker gets the public \\(N\\) values of all users and simply combines \\((N_1,N_2)\\) pairwise to solve Euclid's algorithm for the great common divisor, and a successful solution gives a prime factor \\(p\\), and a simple division gives the other prime factor \\(q\\). With \\(p\\) and \\(q\\), the attacker can immediately compute the user's private key \\(d\\). This is the non-coprime modulus attack.
When applying textbook RSA, if both the public exponent \\(e\\) and the plaintext \\(m\\) are small, such that \\(c=m^e<N\\), the plaintext \\(m\\) can be obtained by directly calculating the \\(e\\)th root of the ciphertext \\(c\\). Even if \\(m^e>N\\) but not large enough, then since \\(m^e=c+k⋅N\\), you can loop through the small \\(k\\) values to perform brute-force root extraction cracking. Here is the Python routine:
\ndef crack_small(c, e, N, repeat)
times = 0
msg = 0
for k in range(repeat):
m, is_exact = gmpy2.iroot(c + times, e)
if is_exact and pow(m, e, N) == c:
msg = int(m)
break
times += N
return msg
Textbook RSA is deterministic, meaning that the same plaintext \\(m\\) always generates the same ciphertext \\(c\\). This makes codebook attack possible: the attacker precomputes all or part of the \\(m\\to c\\) mapping table and saves, then simply searches the intercepted ciphertext for a match. Determinism also means that textbook RSA is not semantically secure and that the ciphertext can reveal some information about the plaintext. Repeated occurrences of the ciphertext indicate that the sender is sending the same message over and over again.
Textbook RSA is malleable, where a particular form of algebraic operation is performed on the ciphertext and the result is reflected in the decrypted plaintext. For example, if there are two plaintexts \\(m_1\\) and \\(m_2\\), and encryption yields \\(c_1=m_1^e\\bmod N\\) and \\(c_2=m_2^e\\bmod N\\), what does \\((c_1⋅c_2)\\) decryption yield? Look at the following equation: \\[(c_1⋅c_2)^d\\equiv m_1^{ed}⋅m_2^{ed}\\equiv m_1⋅m_2\\pmod N\\] So the plaintext obtained after decrypting the product of the two ciphertexts is equal to the product of the two plaintexts. This feature is detrimental to RSA encryption systems in general and provides an opportunity for chosen-ciphertext attack. The following are two examples of attack scenarios:
\nImagine that there is an RSA decryption machine that can decrypt messages with an internally saved private key \\((N,d)\\). For security reasons, the decryptor will reject repeated input of the same ciphertext. An attacker, Marvin, finds a piece of ciphertext \\(c\\) that is rejected by the decryptor when he enters it directly because the ciphertext \\(c\\) has been decrypted before. Marvin finds a way to crack it. He prepares a plaintext \\(r\\) himself, encrypts it with the public key \\((N,e)\\) to generate a new ciphertext \\(c'={r^e}c\\bmod N\\), and then feeds the ciphertext \\(c'\\) to the decryptor. The decryption machine has not decrypted this new ciphertext, so it will not reject it. The result of the decryption is \\[m'\\equiv (c')^d\\equiv r^{ed}c^d\\equiv rm\\pmod N\\] Now that Marvin has \\(m'\\), he can calculate \\(m\\) using the formula \\(m\\equiv m'r^{-1}\\pmod N\\).
Suppose Marvin wants Bob to sign a message \\(m\\), but Bob refuses to do so after reading the message content. Marvin can achieve his goal by using an attack called blinding4. He picks a random message \\(r\\), generates \\(m'={r^e}m\\bmod N\\), and then takes \\(m'\\) to Bob to sign. Bob probably thinks \\(m'\\) is irrelevant and signs it. The result of Bob's signature is \\(s'=(m')^d\\bmod N\\). Now Marvin has Bob's signature on the original message \\(m\\) using the formula \\(s=s'r^{-1}\\bmod N\\). Why? The reason is that \\[s^e\\equiv (s')^er^{-e}\\equiv (m')^{ed}r^{-e}\\equiv m'r^{-e}\\equiv m\\pmod N\\]
The above is by no means a complete list of elementary attack methods, but they are illustrative. In practical RSA applications, we must be very careful and should do the following:
\nFor the textbook RSA deterministic and malleable flaws, and possible brute-force root extraction cracking vulnerabilities, the padding with random elements method can be used to protect against them, and the protection is valid due to the following:
\nUsing low public exponent is dangerous, and there are advanced attacks in the case of non-padding or improper padding, even if brute-force root extraction cracking does not succeed.
\nDiscovered by Swedish theoretical computer scientist Johan Håstad 5, hence the name Håstad's Broadcast Attack. Consider this simplified scenario, assuming that Alice needs to send the same message \\(m\\) to Bob, Carol, and Dave. The public keys of the three recipients are \\((N_1,3)\\), \\((N_2,3)\\), and \\((N_3,3)\\), i.e., the public exponent is all 3 and the public key modulus is different for each. The messages are not padded and Alice directly encrypts and sends three ciphertexts \\(c_1,c_2,c_3\\) using the public keys of the other three:
\n\\[\\begin{cases}\nc_1=m^3\\bmod N_1\\\\\nc_2=m^3\\bmod N_2\\\\\nc_3=m^3\\bmod N_3\n\\end{cases}\\]
\nAt this point Eve secretly writes down the three ciphertexts, marking \\(M=m^3\\), and if she can recover \\(M\\), running a cube root naturally yields the plaintext \\(m\\). Obviously, the common modulus attack does not hold here, and we can also assume that the moduli are pairwise coprime, or else decomposing the modulus using the non-coprime modulus attack will work. So does Eve have a way to compute \\(M\\)? The answer is yes.
\nIn fact, the equivalent problem for solving \\(M\\) here is: Is there an efficient algorithm for solving a number that has known remainders of the Euclidean division by several integers, under the condition that the divisors are pairwise coprime? This efficient algorithm is Chinese Remainder Theorem!
\nThe Chinese remainder theorem gives the criterion that a system of one-element linear congruence equations has a solution and the method to solve it. For the following system of one-element linear congruence equations (be careful not to confuse it with the mathematical notation used to describe the attack scenario above):
\n\\[(S) : \\quad \\left\\{ \n\\begin{matrix} x \\equiv a_1 \\pmod {m_1} \\\\\nx \\equiv a_2 \\pmod {m_2} \\\\\n\\vdots \\qquad\\qquad\\qquad \\\\\nx \\equiv a_n \\pmod {m_n} \\end\n{matrix} \\right.\\]
\nSuppose that the integers \\(m_1,m_2,\\ldots,m_n\\) are pairwise coprime, then the system of equations \\((S)\\) has a solution for any integer \\(a_1,a_2,\\ldots,a_n\\) and the general solution can be constructed in four steps as follows:
\n\\[\\begin{align}\nM &= m_1 \\times m_2 \\times \\cdots \\times m_n = \\prod_{i=1}^n m_i \\tag{1}\\label{eq1}\\\\\nM_i &= M/m_i, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{2}\\label{eq2}\\\\\nt_i M_i &\\equiv 1\\pmod {m_i}, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{3}\\label{eq3}\\\\\nx &=kM+\\sum_{i=1}^n a_i t_i M_i\\tag{4}\\label{eq4}\n\\end{align}\\]
\nThe last line above, Eq. (4) gives the formula of the general solution. In the sense of modulus \\(M\\), the unique solution is \\(\\sum_{i=1}^n a_i t_i M_i \\bmod M\\).
\nTry to solve the things whose number is unknown problem at the beginning of this article by using the Chinese remainder theorem
\nFirst, correspond the variable symbols to the values: \\[m_1=3,a_1=2;\\quad m_2=5,a_2=3;\\quad m_3=7,a_3=2\\] Then calculate \\(M=3\\times5\\times7=105\\), which in turn leads to the derivation of: \\[\\begin{align}\nM_1 &=M/m_1=105/3=35,\\quad t_1=35^{-1}\\bmod 3 = 2\\\\\nM_2 &=M/m_2=105/5=21,\\quad t_2=21^{-1}\\bmod 5 = 1\\\\\nM_3 &=M/m_3=105/7=15,\\quad t_3=15^{-1}\\bmod 7 = 1\\\\\n\\end{align}\\] Finally, take these into the general solution formula: \\[x=k⋅105+(2⋅35⋅2+3⋅21⋅1+2⋅15⋅1)=k⋅105+233\\] So the smallest positive integer solution concerning modulus 105 is \\(233\\bmod 105=23\\)。
\nIn his mathematical text \"Suanfa Tongzong\", Cheng Dawei, a mathematician of the Ming Dynasty in the 16th century, compiled the solutions recorded by the mathematician Qin Jiushao of the Song Dynasty in the \"Mathematical Treatise in Nine Sections\" into a catchy \"Sun Tzu's Song\":
\n\n\nThree friends set out with seventy rare
\n
\nTwenty-one blossoms on five trees of plums
\nSeven men reunited at the half-month
\nAll be known once divided by one hundred and five
Here we must admire the wisdom of the ancient Chinese who, in the absence of a modern mathematical symbol system, were able to derive and summarize such an ingenious solution, contributing an important mathematical theorem to mankind.
\n\nSo Eve just applies the solution of the Chinese Remainder Theorem, computes \\(M\\), and then finds its cube root to get the plaintext \\(m\\), and the attack succeeds. More generally, setting the number of receivers to \\(k\\), if all receivers use the same \\(e\\), then this broadcast attack is feasible as long as \\(k\\ge e\\).
\nHåstad further proves that even if padding is used to prevent broadcast attacks, if the messages generated by the padding scheme are linearly related to each other, such as using the formula \\(m_i=i2^b+m\\) (\\(b\\) is the number of bits of \\(m\\)) to generate the message sent to the receiver \\(i\\), then the broadcast attack can still recover the plaintext \\(m\\) as long as \\(k>e\\). The broadcast attack in this case is still based on the Chinese remainder theorem, but the specific cracking method depends on the information of the linear relationship.
\nTo summarize the above analysis, to prevent the broadcast attack, we must use a higher public exponent \\(e\\) and apply random padding at the same time. Nowadays, the common public key exponent \\(e\\) is 65537 (\\(2^{16}+1\\)), which can balance the efficiency and security of message encryption or signature verification operations.
\nLast, Python routines for simulating broadcast attacks are given as follows:
\ndef solve_crt(ai: list, mi: list): |
This code uses two methods to simulate the broadcast attack. One calls the generic Chinese remainder theorem solver function solve_crt()
and then gets the cube root of the result; the other calls the special broadcast attack function rsa_broadcast_attack()
for the public key index \\(e=3\\), which directly outputs the cracked plaintext value. The internal implementation of these two functions is based on the generalized formula of the Chinese remainder theorem, and the output results should be identical. The cracked plaintext value is then input to the uint_to_bytes()
function, which is converted into a byte array to compare with the original quote
. Note that the program uses objects generated by the RSA class to simulate the receivers Bob, Carroll, and Dave, and the implementation of the RSA class is omitted here given the limitation of space.
\n\nNext article: RSA: Attack and Defense (II)
\n
American computer scientist and security expert Gary McGraw has a famous piece of advice for software developers - \"never roll your own cryptography\"↩︎
The original RSA paper (Part IX, Section C) did mention Miller's algorithm for factoring \\(N\\) with a known \\(d\\). This algorithm also applies to \\(d\\) generated by the Carmichael function \\(\\lambda(N)\\).↩︎
gmpy2 is a Python extension module written in C that supports multi-precision arithmetic.↩︎
On some special occasions, blinding can be used for effective privacy protection. For example, in cryptographic election systems and digital cash applications, the signer and the message author can be different.↩︎
Johan Håstad, a Swedish theoretical computer scientist, a professor at the KTH Royal Institute of Technology, and a Fellow of the American Mathematical Society (AMS) and an Association for Computing Machinery (ACM) fellow.↩︎
In March 2021, the Internet Engineering Task Force (IETF) released RFC 8996, classified as a current best practice, officially announcing the deprecation of the TLS 1.0 and TLS 1.1 protocols. If your applications and web services are still using these protocols, please stop immediately and update to TLS 1.2 or TLS 1.3 protocol versions as soon as possible to eliminate any possible security risks.
\nOne single vulnerability is all an attacker needs.
— Window Snyder (American computer security expert, former Senior Security Strategist at Microsoft, and has been a top security officer at Apple, Intel and other companies)
The document title of RFC 8996 is quite straightforward, \"Deprecating TLS 1.0 and TLS 1.1\". So what is the rationale it gives? Here is a simple interpretation.
\nFirst, take a look at its abstract:
\n\n\nThis document formally deprecates Transport Layer Security (TLS) versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those documents have been moved to Historic status. These versions lack support for current and recommended cryptographic algorithms and mechanisms, and various government and industry profiles of applications using TLS now mandate avoiding these old TLS versions. TLS version 1.2 became the recommended version for IETF protocols in 2008 (subsequently being obsoleted by TLS version 1.3 in 2018), providing sufficient time to transition away from older versions. Removing support for older versions from implementations reduces the attack surface, reduces opportunity for misconfiguration, and streamlines library and product maintenance.
\nThis document also deprecates Datagram TLS (DTLS) version 1.0 (RFC 4347) but not DTLS version 1.2, and there is no DTLS version 1.1.
\nThis document updates many RFCs that normatively refer to TLS version 1.0 or TLS version 1.1, as described herein. This document also updates the best practices for TLS usage in RFC 7525; hence, it is part of BCP 195.
\n
The information given here is clear, the reasons for deprecating them are purely technical. TLS 1.0 and TLS 1.1 cannot support stronger encryption algorithms and mechanisms, and cannot meet the high-security requirements of various network applications in the new era. TLS is TCP-based. Corresponding to the UDP-based DTLS protocol, RFC 8996 also announced the deprecation of the DTLS 1.0 protocol.
\nThe Introduction section lists some details of the technical reasons:
\nClauses 5 and 6 above are clear and need no further explanation.
\nFor 3DES mentioned in Clause 1, although it uses three independent keys with a total length of 168 bits, considering the possible meet-in-the-middle_attack attack, its effective key strength is only 112 bits. Also, the 3DES encryption block length is still 64 bits, which makes it extremely vulnerable to birthday attack (see Sweet32). NIST stipulates that a single 3DES key group can only be used for encrypting \\(2^{20}\\) data blocks (ie 8MB). This was of course too small, and eventually, NIST decided in 2017 to deprecate 3DES in the IPSec and TLS protocols.
\n3DES is just one example, another category that has been phased out earlier is cipher suites that use RC4 stream ciphers, see RFC 7465 for details. In addition, there are various problems in the implementation of block cipher CBC mode, which are often exploited by attackers to crack TLS sessions. A summary of various attacks and countermeasures of TLS 1.0 and TLS 1.1 is described in detail in NIST800-52r2 and RFC7457. These two reference documents provide the key rationale for deprecation. Obviously, any protocol that mandates the implementation of insecure cipher suites should be on the list to be eliminated.
\nIn the second section of the document, the content in Section 1.1 \"The History of TLS\" of NIST800-52r2 is directly quoted (abbreviated as shown in the following table):
\nTLS Version | \nProtocol Document | \nKey Feature Update | \n
---|---|---|
1.1 | \nRFC 4346 | \nImproved initialization vector selection and padding error processing to address weaknesses discovered on the CBC mode of operation defined in TLS 1.0. | \n
1.2 | \nRFC 5246 | \nEnhanced encryption algorithms, particularly in the area of hash functions, can support SHA-2 series algorithms for hashing, MAC, and pseudorandom function computations, also added AEAD cipher suite. | \n
1.3 | \nRFC 8446 | \nA significant change to TLS that aims to address threats that have arisen over the years. Among the changes are a new handshake protocol, a new key derivation process that uses the HMAC-based Extract-and-Expand Key Derivation Function (HKDF), and the removal of cipher suites that use RSA key transport or static Diffie-Hellman key exchanges, the CBC mode of operation, or SHA-1. | \n
AEAD is an encryption mode that can guarantee the confidentiality, integrity, and authenticity of data at the same time, typically such as CCM and GCM. TLS 1.2 introduced a range of AEAD cipher suites, and its high security made it the exclusive choice for TLS 1.3. These annotate Clause 2 of technical reasons.
\nClauses 3 and 4 of technical reasons call out SHA-1, so what is the problem with SHA-1? Section 3 of the document cites a paper by two French researchers, Karthikeyan Bhargavan and Gaetan Leurent .
\nAs a cryptographic hash function, SHA-1 was designed by the National Security Agency (NSA) and then published as a Federal Information Processing Standard (FIPS) by the National Institute of Standards and Technology (NIST). SHA-1 can process a message up to \\(2^{64}\\) bits and generate a 160-bit (20-byte) hash value known as the message digest. Therefore, the complexity of brute force cracking based on birthday attack is \\(2^{80}\\) operations. In 2005, Chinese cryptographer Wang Xiaoyun and her research team made a breakthrough in this field. The high-efficiency SHA-1 attack method they published can be used to find a hash collision within a computational complexity of \\(2^{63}\\). This has brought a huge impact on the security of SHA-1, but it does not mean that the cracking method can enter the practical stage.
\nNetwork security protocols (such as TLS, IKE, and SSH, etc.) rely on the second preimage resistance of cryptographic hash functions, that is, it is computationally impossible to find any secondary input value that has the same output as a specific input value. For example, for a cryptographic hash function \\(h(x)\\) and given input \\(x\\), it is difficult to find a sub-preimage \\(x^′ ≠ x\\) that is satisfying \\(h(x) = h(x^′)\\). Because finding a hash collision does not mean that a sub-preimage can be located, in practice, it was once thought that continuing to use SHA-1 is not a problem.
\nHowever, in 2016, Bhargavan and Leurent (who implemented the aforementioned Sweet32 attack against 64-bit block ciphers) discovered a new class of methods to attack key exchange protocols that shattered this perception. These methods are based on the principle of the chosen prefix collision attack. That is, given two different prefixes \\(p_1\\) and \\(p_2\\), the attack finds two appendages \\(m_1\\) and \\(m_2\\) such that \\(h(p_1 ∥ m_1) = hash(p_2 ∥ m_2)\\). Using this approach, they demonstrated a man-in-the-middle attack against TLS clients and servers to steal sensitive data, and also showed that the attack could be used to masquerade and downgrade during TLS 1.1, IKEv2, and SSH-2 session handshakes. In particular, they proved that with only \\(2^{77}\\) operations the handshake protocol using SHA-1 or MD5 and SHA-1 concatenated hash values could be cracked.
\nSince neither TLS 1.0 nor TLS 1.1 allows the peers to choose a stronger cryptographic hash function for signatures in the ServerKeyExchange or CertificateVerify messages, the IETF confirmed that using a newer protocol version is the only upgrade path.
\nSections 4 and 5 of the document again clarify that TLS 1.0 and TLS 1.1 must not be used, and negotiation to TLS 1.0 or TLS 1.1 from any TLS version is not allowed. This means that ClientHello.client_version and ServerHello.server_version issued by the TLS client and server, respectively, must not be {03,01} (TLS 1.0) or {03,02} (TLS 1.1). If the protocol version number in the Hello message sent by the other party is {03,01} or {03,02}, the local must respond with a \"protocol_version\" alert message and close the connection.
\nIt is worth noting that due to historical reasons, the TLS specification does not specify the value of the record layer version number (TLSPlaintext.version) when the client sends the ClientHello message. So to maximize interoperability, TLS servers MUST accept any value {03,XX} (including {03,00}) as the record layer version number for ClientHello messages, but they MUST NOT negotiate TLS 1.0 or 1.1.
\nSection 6 of the document declares a textual revision to the previously published RFC 7525 (Recommendations for the Secure Use of TLS and DTLS). Three places in this RFC change implementation-time negotiations of TLS 1.0, TLS 1.1, and DTLS 1.0 from \"SHOULD NOT\" to \"MUST NOT\". The last section is a summary of standard RFC operations and security considerations.
\nIn the industry of large public online services, GitHub was the first to act. They started disabling TLS 1.0 and TLS 1.1 in all HTTPS connections back in February 2018, while also phasing out insecure diffie-hellman-group1-sha1
and diffie-hellman-group14-sha1
key exchange algorithms in the SSH connection service. In August 2018, Eric Rescorla, CTO of Mozilla Firefox, published the TLS 1.3 technical specification RFC 8446. Two months later, Mozilla issued a statement together with the three giants of Apple, Google, and Microsoft, and put the deprecation of TLS 1.0 and TLS 1.1 on the agenda.
The following is a brief summary of the actions of several related well-known companies:
\nSecurity.framework
symbols from the app\nBoth TLS/DTLS clients and servers need to be tested to verify that their implementations follow the current best practices of RFC 8996.
\nQualys originated as a non-commercial SSL Labs Projects. They offer a free and simple client and server testing service, as well as a monitoring panel reporting TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics for November 2022.
\nProtocol Version | \nSecurity | \nSupporting Sites (Oct. 2022) | \nSupporting Site (Nov. 2022) | \n% Change | \n
---|---|---|---|---|
SSL 2.0 | \nInsecure | \n316(0.2%) | \n303(0.2%) | \n-0.0% | \n
SSL 3.0 | \nInsecure | \n3,015(2.2%) | \n2,930(2.2%) | \n-0.0% | \n
TLS 1.0 | \nDeprecated | \n47,450(34.9%) | \n46,691(34.4) | \n-0.5% | \n
TLS 1.1 | \nDeprecated | \n51,674(38.1%) | \n50,816(37.5%) | \n-0.6% | \n
TLS 1.2 | \nDepending on the Cipher Suite and the Client | \n135,557(99.8) | \n135,445(99.9) | \n+0.1% | \n
TLS 1.3 | \nSecure | \n78,479(57.8%) | \n79,163(58.4%) | \n+0.6% | \n
As you can see, almost 100% of sites are running TLS 1.2, and the percentage of TLS 1.3 support is close to 60%. This is very encouraging data. While very few sites are still running SSL 2.0/3.0 and TLS 1.0/1.1 are both still supported at around 35%, overall their percentages are continuing to decline and this good trend should continue.
\nThis blog site is served by GitHub Page, enter the URL to SSL Server Test page and submit it to get a summary of the test results as follows.
\nThe site achieved the highest overall security rating of A+. It got a perfect score for certificate and protocol support, and a 90 for both key exchange and password strength. This shows that GitHub fulfills its security promises to users and deserves the trust of programmers.
\nThe configuration section of the report gives details of the test results for protocol support and cipher suites as follows.
\nThis further confirms that the GitHub Page only supports TLS 1.2/1.3, as required by RFC 8996. It can also be seen that under the \"Cipher Suites\" subheading, TLS 1.3 shows two GCMs and one ChaCha20-Poly1305, which are all cipher suites based on the AEAD algorithms. Three cipher suites of the same type are the preferred TLS 1.2 cipher suites for the server as well. This is exactly the current commonly adopted configuration of secure cryptographic algorithms.
\nIf you suspect that a private server is still using the outdated TLS/SSL protocol, you can do a simple test with the command line tool curl
, an example of which is as follows.
❯ curl https://www.cisco.com -svo /dev/null --tls-max 1.1 |
Here enter the command line option -tls-max 1.1
to set the highest protocol version 1.1 and connect to the Cisco home page. The output shows that the connection failed and that a \"protocol version\" alert message was received. This indicates that the server has rejected the TLS 1.1 connection request, and the response is exactly what is required by RFC 8996.
The openssl
command line tool provided by the general purpose open source cryptography and secure communication toolkit OpenSSL can also do the same test. To test whether the server supports the TLS 1.2 protocol, use the option s_client
to emulate a TLS/SSL client and also enter -tls1_2
to specify that only TLS 1.2 is used. The command line runs as follows.
❯ openssl s_client -connect www.cisco.com:443 -tls1_2 |
This record is very detailed and the format is very readable. From the output, it can be understood that the digital certificate of the Cisco home page server is digitally signed and certified by the root certificate authority IdenTrust. The client-server session is built on the TLS 1.2 protocol, and the selected cipher suite is ECDHE-RSA-AES128-GCM-SHA256 of type AEAD, which is identical to the preferences provided by the GitHub Page.
\nIf you are not sure about the security of your browser and want to test whether it still supports the pre-TLS 1.2 protocols, you can enter the following URL in your browser's address bar.
\nAfter connecting to the second URL with the default configuration of Firefox, the page shows the following
\n\n\nSecure Connection Failed
\nAn error occurred during a connection to tls-v1-1.badssl.com:1011. Peer using unsupported version of security protocol.
\nError code: SSL_ERROR_UNSUPPORTED_VERSION
\n\n
\n- The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
\n- Please contact the website owners to inform them of this problem.
\nThis website might not support the TLS 1.2 protocol, which is the minimum version supported by Firefox.
\n
This error message clearly indicates that Firefox is running a minimum TLS protocol version of 1.2 in this configuration, and since the other side is only running TLS 1.1, the two sides cannot establish a connection.
\nSo what is the result of the connection when the browser does still retain TLS 1.0/1.1 functionality?
\nFor testing purposes, you can first change the default TLS preference value of Firefox to 1.1 by following the steps below (refer to the figure below).
\nAt this point, then connect to https://tls-v1-1.badssl.com, the result is
\nThis bold red page tells you that the browser you are currently using does not have TLS 1.1 disabled and is a security risk, so try not to use it if you can.
\nAfter testing, don't forget to restore the default TLS minimum version setting (3) for Firefox.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
Besides NIST and RFC documents, For an in-depth study of the TLS protocol specification, system implementation, and application deployment, a careful reading of the following three books is recommended.
\n\nTLS (Transport Layer Security) is a cryptographic protocol to secure network communication. TLS 1.3 is the latest version of the TLS protocol, succeeding TLS 1.2. TLS 1.3 aims to provide more robust security, higher privacy protection, as well as better performance than previous versions. Here is a brief introduction to TLS 1.3. Also, we discuss NIST's requirement for TLS 1.3 readiness and give examples of enabling TLS 1.3 in some commonly used web servers.
\nIt takes 20 years to build a reputation and a few minutes of cyber-incident to ruin it.
— Stéphane Nappo (Vice President and Global Chief Information Security Officer of Groupe SEB, France, 2018 Global CISO of the year)
TLS 1.3 is the latest recommended cryptographic protocol for protecting a wide variety of network communications, including web browsing, email, online trading, instant messaging, mobile payments, and many other applications. By using TLS 1.3, more secure and reliable communication connections can be established, ensuring confidentiality, authenticity, and data integrity. It was standardized by the Internet Engineering Task Force (IETF) in August 2018, and published as RFC 8446.
\nTLS 1.3 introduces some important improvements over TLS 1.2. The table below presents a quick comparison of the two:
\nAspect | \nTLS 1.2 | \nTLS 1.3 | \n
---|---|---|
Protocol Design | \nRequest-response model | \nReduced round trips | \n
Handshake | \nMultiple round trips | \nSingle round trip | \n
Cipher Suites | \nSupports wide range, including insecure ones | \nFocuses on stronger algorithms | \n
Security | \nKnown vulnerabilities, e.g., CBC vulnerabilities | \nAddresses previous issues, stronger security | \n
Performance | \nHigher latency due to more round trips | \nFaster connection establishment | \n
Resilience to Attacks | \nVulnerable to downgrade attacks and padding oracle attacks | \nAdditional protections against attacks | \n
Compatibility | \nWidely supported across platforms | \nIncreasing support, may not be available on older systems | \n
Implementation Supports | \nAvailable in many cryptographic libraries | \nSupported in various libraries | \n
It can be seen that enhanced security and performance improvements are the most notable features of TLS 1.3, and we can explore more into these in the following sections.
\nThe protocol design principle of TLS 1.3 has enhanced security as its primary goal. As a result, TLS 1.3 drastically reduces the number of supported cipher suites. It removes insecure and weak cipher suites, leaving only more secure and modern cipher suites. This helps to increase the security of communications and avoids the use of outdated or vulnerable cipher suites.
\nSpecifically, TLS 1.3 removes various cipher suites that use static RSA key transport, static Diffie-Hellman key exchange, CBC mode of operation, or SHA-1. It adopts only a limited number of Authenticated Encryption with Associated Data (AEAD) cipher suites. AEAD can guarantee the confidentiality, integrity, and authenticity of data at the same time, and its high security makes it the exclusive choice for TLS 1.3.
\nOn the other hand, the name string of the cipher suite used in previous TLS versions included all algorithms for key exchange, digital signatures, encryption, and message authentication. Each cipher suite is assigned a 2-byte code point in the TLS Cipher Suites registry managed by the Internet Assigned Numbers Authority (IANA). Every time a new cryptographic algorithm is introduced, a series of new combinations need to be added to the list. This has led to an explosion of code points representing every valid choice of these parameters. This situation also makes the selection of cipher suites complicated and confusing.
\nThe design of TLS 1.3 changed the concept of the cipher suite. It separates the authentication and key exchange mechanisms from the record protection algorithm (including secret key length) and a hash to be used with both the key derivation function and handshake message authentication code (MAC). The new cipher suite naming convention is TLS_<AEAD>_<Hash>
, where the hash algorithm is used for the newly defined key derivation function HKDF of TLS 1.3 and the MAC generation in the handshake phase. The cipher suites defined by the TLS 1.3 protocol are:
+------------------------------+-------------+ |
This simplified cipher suite definition and greatly reduced set of negotiation parameters also speed up TLS 1.3 handshake, improving overall performance.
\nTLS 1.3 emphasizes forward secrecy, ensuring that the confidentiality of communications is protected even if long-term secrets used in the session key exchange are compromised. It only allows key exchange based on ephemeral Diffie-Hellman key exchange (DHE) or ephemeral elliptic curve Diffie-Hellman key exchange (ECDHE). Both have the property of forward secrecy. Also, the protocol explicitly restricts the use of secure elliptic curve groups and finite field groups for key exchange:
\n/* Elliptic Curve Groups (ECDHE) */ |
The above elliptic curve groups for ECDHE are specified by RFC 8422. The first three are defined by the FIPS.186-4 specification and the corresponding NIST names are P-256/P-384/P-512, while the next two (x25519/x448) are recommended by ANSI.X9-62.2005. RFC 7919 specifies four finite field groups (ffdhe####) for DHE. The primes in these finite field groups are all safe primes.
\nIn number theory, a prime number \\(p\\) is a safe prime if \\((p-1)/2\\) is also prime.
\nFor signature verification in the key exchange phase, TLS 1.3 introduces more signature algorithms to meet different security requirements:
\nTLS 1.3 stops using the DSA (Digital Signature Algorithm) signature algorithm. This is also a notable difference from TLS 1.2. DSA has some security and performance limitations and is rarely used in practice, so TLS 1.3 removed support for DSA certificates.
\nAdditionally, TLS 1.3 includes the following improvements to enhance security
\nServerHello
message during the TLS 1.3 handshake are now encrypted. The newly introduced EncryptedExtensions
message enables encryption protection of various extensions previously sent in plain text.Certificate
messages sent from the server to the client. This encryption prevents threats such as man-in-the-middle attacks, information leakage, and certificate forgery, further fortifying the security and privacy of the connection.The general trend towards high-speed mobile Internet requires the use of HTTPS/TLS to protect the privacy of all traffic as much as possible. The downside of this is that new connections can become a bit slower. For the client and web server to agree on a shared key, both parties need to exchange security attributes and related parameters through the TLS \"handshake process\". In TLS 1.2 and all protocols before it, the initial handshake process required at least two round-trip message transfers. Compared to pure HTTP, the extra latency introduced by the TLS handshake process of HTTPS can be very detrimental to performance-conscious applications.
\nTLS 1.3 greatly simplifies the handshake process, requiring only one round trip in most cases, resulting in faster connection establishment and lower latency. Every TLS 1.3 connection will use (EC)DHE-based key exchange, and the parameters supported by the server may be easy to guess (such as ECDHE + x25519 or P-256). Since the options are limited, the client can directly send the (EC)DHE key share information in the first message without waiting for the server to confirm which key exchange it is willing to support. This way, the server can derive the shared secret one round in advance and send encrypted data.
\nThe following diagram compares the message sequences of the handshake process of TLS 1.2 and TLS 1.3. Both operate with public key-based authentication. The TLS 1.3 handshake shown below uses the symbols borrowed from the RFC 8446 specification: '+' indicates a noteworthy extension; '*' indicates an optional message or extension; '[]', '()', and '{}' represent encrypted messages, where the keys used for encryption are different.
\nThis figure illustrates the following points:
\nServerHelloDone
, ChangeCipherSpec
, ServerKeyExchange
, and ClientKeyExchange
. The contents of TLS 1.2's ServerKeyExchange
and ClientKeyExchange
messages vary depending on the authentication and key-sharing method being negotiated. In TLS 1.3, this information was moved to the extensions of ClientHello
and ServerHello
messages. TLS 1.3 completely deprecates ServerHelloDone
and ChangeCipherSpec
messages, there is no replacement.ClientHello
message carries four extensions that are must-haves in this mode: key_share
, signature_algorithms
, supported_groups
, and support_versions
.ClientKeyExchange
and ChangeCipherSpec
messages are carried in separate packets, and the Finished
message is the first (and only) encrypted handshake message. The whole process needs to transmit 5-7 data packets.Application Data
is already sent by the client after the first round trip. As mentioned earlier, the EncryptedExtension
message provides privacy protection for ServerHello
extensions in earlier versions of TLS. If mutual authentication is required (which is common in IoT deployments), the server will send a CertificateRequest
message.Certificate
, CertificateVerify
, and Finished
messages in TLS 1.3 retain the semantics of earlier TLS versions, but they are all asymmetrically encrypted now. Echoing the description in the last section, by encrypting Certificate
and CertificateVerify
messages, TLS 1.3 better protects against man-in-the-middle and certificate forgery attacks while enhancing the privacy of connections. This is also an important security feature in the design of TLS 1.3.In rare cases, when the server does not support a certain key-sharing method sent by the client, the server can send a new HelloRetryRequest
message letting the client know which groups it supports. As the group list has shrunk significantly, this is not expected to happen very often.
0-RTT (Zero Round Trip Time) in TLS 1.3 is a special handshake mode. It allows clients to send encrypted data during the handshake phase, reducing the number of round trips required for connection establishment and enabling faster session resumption. The following is a brief explanation of the 0-RTT working mode:
\nearly_data
extension of the ClientHello
message, along with encrypted Application Data
. The client encrypts 0-RTT data using a pre-shared key (PSK) obtained from a previous connection.EncryptedExtensions
message, and then confirms the connection in the Finished
message. This way, the server can quickly establish a secure connection with 0 round trips. It can also immediately send data to the client to achieve 0-RTT data transmission.The message sequence of the 0-RTT session resumption and data transmission process of TLS 1.3 is as follows:
\nDoes the TLS 1.3 protocol allow the use of RSA digital certificates?
\nA common misconception is that \"TLS 1.3 is not compatible with RSA digital certificates\". The description in the \"Signature Verification\" section above shows that this is wrong. TLS 1.3 still supports the use of RSA for key exchange and authentication. However, considering the limitations of RSA, it is recommended that when building and deploying new TLS 1.3 applications, ECDHE key exchange algorithms and ECC digital certificates are preferred to achieve higher security and performance.
During the TLS 1.3 handshake, how does the server request the client to provide a certificate?
\nIn some scenarios, the server also needs to verify the identity of the client to ensure that only legitimate clients can access server resources. This is the case with mTLS (mutual TLS). During the TLS 1.3 handshake, the server can specify that the client is required to provide a certificate by sending a special CertificateRequest
extension. When the server decides to ask the client for a certificate, it sends a CertificateRequest
extension message after the ServerHello
message. This extended message contains some necessary parameters, such as a list of supported certificate types, a list of acceptable certificate authorities, and so on. When the client receives it, it knows that the server asked it for a certificate, and it can optionally respond to the request. If the client is also configured to support mTLS and decides to provide a certificate, it provides its certificate chain by sending a Certificate
message.
Is 0-RTT vulnerable to replay attacks?
\nTLS 1.3's 0-RTT session resumption mode is non-interactive and does risk replay attacks in some cases. An attacker may repeat previously sent data to simulate a legitimate request. To avoid and reduce the risk of replay attacks to the greatest extent, TLS 1.3 provides some protection measures and suggestions:
\nClientHello
message, and reject duplicates. Logging all ClientHello
s would cause the state to grow without bound, but combined with #2 above, the server can log ClientHello
s within a given time window and use obfuscated_ticket_age
to ensure that tickets are not duplicated outside the window use.If the client does not know whether the server supports TLS 1.3, how could it negotiate the TLS version via handshake?
\nThe TLS protocol provides a built-in mechanism for negotiating the running version between endpoints. TLS 1.3 continues this tradition. RFC 8446 Appendix D.1 \"Negotiating with an Older Server\" gives specific instructions:
\n\n\nA TLS 1.3 client who wishes to negotiate with servers that do not support TLS 1.3 will send a normal TLS 1.3 ClientHello containing 0x0303 (TLS 1.2) in ClientHello.legacy_version but with the correct version(s) in the \"supported_versions\" extension. If the server does not support TLS 1.3, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation will proceed as appropriate for the negotiated protocol.
\n
The following screenshot of a TLS 1.3 ClientHello
message decode demonstrates this. The version number of the handshake message displayed on the left is \"Version: TLS 1.2 (0x0303)\". At the same time, it can be seen that the cipher suite section first lists 3 TLS 1.3 AEAD cipher suites, followed by 14 TLS 1.2 regular cipher suites. On the right, there are 4 extensions - key_share
, signature_algorithms
, supported_groups
, and support_versions
. The support_versions
extension includes both TLS 1.3 and TLS 1.2 version numbers. This is the TLS version list for the server to choose from. Additionally, the key_share
extension includes the client's preferred key-sharing method as x25519 and secp256r1(i.e. NIST P-256)
Does the TLS 1.3 protocol work with UDP and EAP?
\nTLS was originally designed for TCP connections, and a variant DTLS (Datagram Transport Layer Security) for UDP was introduced later. Based on TLS 1.3, IETF has released the corresponding upgraded version of the DTLS 1.3 protocol RFC 9147. The design goal of DTLS 1.3 is to provide \"equivalent security guarantees with the exception of order protection / non-replayability\". This protocol was released in April 2022, and currently, there are not many software libraries supporting it.
\nTLS can also be used as an authentication and encryption protocol in various EAP types, such as EAP-TLS, EAP-FAST, and PEAP. Corresponding to TLS 1.3, IETF also published two technical standard documents:
\nBoth protocols are also quite new, and the software library updates supporting them are still some time away.
TLS 1.3 brings new security features and a faster TLS handshake. Since its release in 2018, many Internet services have migrated to this latest version. Nevertheless, widespread adoption across websites takes time. The non-commercial SSL Labs Projects has a dashboard called SSL Pulse that reports TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics by July 2023.
\nAs can be seen, of all 135,000+ probed sites the percentage of TLS 1.3 support is about 63.5%. That means there are still close to 50 thousand sites that do not leverage the security and performance benefits of TLS 1.3. Why? The decision to migrate a website to a new protocol version like TLS 1.3 can be complex and influenced by various factors. The top 3 common reasons hindering TLS 1.3 migration are
\nHowever, for network hardware/software vendors who want their products on the procurement list of any US public sector organization, there is a coming NIST mandate to make TLS 1.3 available by January 2024. This is stipulated in the National Institute of Standards and Technology Special Publication (NIST SP) 800-52 Rev. 2: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations. Quoted from NIST SP 800-52 Rev. 2
\n\n\n3.1 Protocol Version Support
\nServers that support government-only applications shall be configured to use TLS 1.2 and should be configured to use TLS 1.3 as well. ...
\nServers that support citizen or business-facing applications (i.e., the client may not be part of a government IT system)10 shall be configured to negotiate TLS 1.2 and should be configured to negotiate TLS 1.3. ...
\nAgencies shall support TLS 1.3 by January 1, 2024. After this date, servers shall support TLS 1.3 for both government-only and citizen or business-facing applications. In general, servers that support TLS 1.3 should be configured to use TLS 1.2 as well. However, TLS 1.2 may be disabled on servers that support TLS 1.3 if it has been determined that TLS 1.2 is not needed for interoperability.
\n
As in the RFC documents, \"shall\" above is a strong keyword that means that the definition is an absolute requirement of the specification. So this NIST publication requires all servers owned by the US government agencies to be able to support TLS 1.3 by 01/01/2024. They must run a minimum TLS version 1.2 by default and can be configured to do TLS 1.3 only if desired.
\nIt is worth pointing out that this is not an official FIPS requirement, so not mandatory for the FIPS 140-3 certification at present. Besides, this NIPS document has a clear scope statement: \"The scope is further limited to TLS when used in conjunction with TCP/IP. For example, Datagram TLS (DTLS), which operates over datagram protocols, is outside the scope of these guidelines. NIST may issue separate guidelines for DTLS at a later date.\" Based on this, we can infer that DTLS and EAP are out of consideration for this mandate.
\nThe enhanced security and optimized performance of TLS 1.3 make it the first choice for securing communication of various network applications. Now we demonstrate how to enable TLS 1.3 function in three commonly used web server software Apache, Nginx, and Lighttpd.
\nNOTE: The implementation of many secure network communication applications relies on third-party SSL/TLS software libraries, such as wolfSSL, GnuTLS, NSS, and OpenSSL. Therefore, to enable the TLS 1.3 function of these applications, you need to ensure that the libraries they link with support TLS 1.3. For example, in September 2018, the popular OpenSSL project released version 1.1.1 of the library, with support for TLS 1.3 as its \"top new feature\".
\nThe Apache HTTP Server is an open-source web server software from the Apache Software Foundation. Apache HTTP server is widely used and is one of the most popular web server software due to its cross-platform and security. Apache supports a variety of features, many of which extend core functionality through compiled modules, such as authentication schemes, proxy servers, URL rewriting, SSL/TLS support, and compiling interpreters such as Perl/Python into the server.
\nApache HTTP Server has built-in support for TLS 1.3 since version 2.4.36, no need to install any additional modules or patches. The following command can be used to verify the version of the server
\n$ apache2ctl -v |
Once the version is verified, the SSLProtocol
line of the configuration file can be updated. The following will enable the Apache HTTP server to only support the TLS 1.3 protocol
# Only enable TLS 1.3 |
If the server needs to be compatible with clients that support TLS 1.2, you can add +TLSv1.2
. After updating the configuration, restart the service
$ sudo service apache2 restart |
Nginx is a high-performance web server based on an asynchronous framework and modular design. It can also be used for reverse proxy, load balancer, and HTTP caching applications. It is free and open-source software released under the terms of a BSD-like license. Nginx uses an asynchronous event-driven approach to request processing, which can provide more predictable performance under high load. The current market share of Nginx is almost equal to that of the Apache HTTP server.
\nNginx supports TLS 1.3 from version 1.13.0. The following command can be used to verify its version
\n$ nginx -v |
In the Nginx configuration file, find the server block and modify the ssl_protocols
line to enable TLS 1.3:
server { |
If you don't need to continue to support TLS 1.2, delete the TLSv1.2
there. After the modification is complete, you can run the following command to test the configuration of Nginx, and then restart the service
$ sudo nginx -t |
Lighttpd is a lightweight open-source web server software. It focuses on high performance, low memory footprint, and fast responsiveness. Lighttpd is suitable for serving web applications and static content of all sizes. Its design goal is to provide an efficient, flexible, and scalable web server, especially suitable for high-load and resource-constrained (such as embedded systems) environments.
\nThe first Lighttpd release to support TLS 1.3 is version 1.4.56. Starting with this version, the minimum version of TLS that Lighttpd supports by default is TLS 1.2. That is to say, Lighttpd supports TLS 1.2 and TLS 1.3 if no corresponding configuration file modification is made.
\nTo limit the use of Lighttpd to only the TLS 1.3 feature, first make sure the mod_openssl module is loaded. Then in the configuration file lighttpd.conf, find the server.modules
section, and add the following ssl.openssl.ssl-conf-cmd
line:
server.modules += ("mod_openssl") |
This will set the minimum version supported by Lighttpd to be TLS 1.3. Finally, save and reload the Lighttpd configuration for the changes to take effect:
\nsudo lighttpd -t -f /etc/lighttpd/lighttpd.conf # check configuration |
By chance, I came across a picoCTF RSA challenge called Sum-O-Primes. This problem is not difficult, you can do it by knowing the basics of the RSA algorithm. In addition, if you are familiar with the history of the evolution of the RSA algorithm, you can find a second ingenious fast solution.
\npicoCTF is a free computer security education program created by security and privacy experts at Carnegie Mellon University. It uses original content built on the CTF (Capture the Flag) framework to provide a variety of challenges. It provides participants with valuable opportunities to systematically learn cybersecurity knowledge and gain practical experience.
\nThe collection of practice questions for picoCTF is called picoGym. The general problem solution is to search or decipher a string in the format \"picoCTF{...}\" from the given information, that is, the flag to be captured. As shown in the figure below, picoGym currently contains 271 cybersecurity challenge exercises, covering general skills, cryptography, reverse engineering, forensics, and other fields.
\nThere are 50 cryptography-related challenges in picoGym, one of which is Sum-O-Primes. The task of this challenge is simple and explained as follows:
\n\n\nWe have so much faith in RSA we give you not just the product of the primes, but their sum as well!
\n\n
\n- gen.py
\n- output.txt
\n
That is, we not only give the product of the two prime numbers used by RSA but also tell you their sum. How are these given? You need to discover by yourself from the rest of the information. After clicking the two links and downloading the file, open the first Python file:
\n#!/usr/bin/python |
If you have basic Python programming skills and understand the principles of the RSA algorithm, you should be able to read the above program quickly. What it does is:
\nflag.txt
to read the content. Then use the hexlify
and int
functions to convert it to an integer and store the result in a variable FLAG
.get_prime
to generate two prime numbers, store their sum in x
and their product in n
. Then assign 65537 to e
and calculate the RSA private exponent d
.pow
functions to perform modular exponentiation, which implements RSA encryption to encrypt plaintext FLAG
into ciphertext c
.x
, n
, and c
.Open the second file, which is apparently the output of the first program in Python:
\nx = 154ee809a4dc337290e6a4996e0717dd938160d6abfb651736d9f5d524812a659b310ad1f221196ee8ab187fa746a1b488a4079cddfc5db08e78be0d96c83c01e9bb42420b40d6f0ad9f220633459a6dc058bb01c517386bfbd2d4811c9b08558b0e05534768581a74884758d15e15b4ef0dbd6a338bf1f52eed4f137957737d2 |
Once you understand the meaning of the question, you can make a judgment immediately —— if you can decrypt the ciphertext c
and retrieve the plaintext FLAG, you can get the original content of flag.txt
, that is, capture the flag.
RSA decryption requires a private key exponent d
. Referring to the steps of the RSA algorithm below, it is obvious that this demands integer factorization for large prime numbers p
and q
first.
From here, the challenge becomes a problem that, knowing the sum and product of two large prime numbers known, find these two large prime numbers. That is, to solve a system of quadratic linear equations
\n\\[\n\\left\\{\n\\begin{aligned}\np+q &=n \\\\ \np*q &=x\n\\end{aligned} \n\\right. \n\\]
\nUsing the knowledge of elementary mathematics, the above equations can be transformed into a quadratic equation \\[p^2 - x * p + n = 0\\]
\nObviously, \\(p\\) and \\(q\\) are its two roots. According to the quadratic formula
\n\\[(p,q)={\\frac {x}{2}}\\pm {\\sqrt {\\left({\\frac {x}{2}}\\right)^{2}-n}}\\]
\nWe can get \\(p\\) and \\(q\\). The rest of the work is easy. The code to compute \\(d\\) from \\(p\\) and \\(q\\) can be copied directly from lines 28, 30, and 31 in gen.py. The final complete Python problem-solving code is as follows:
\nimport math |
The above program defines a general function solve_rsa_primes
to solve two large prime numbers. After it gets d
, the same pow
function is called to decrypt, and finally the plaintext is converted from a large integer to a byte sequence and printed out. The result of running this program is
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}' |
BINGO! Capture the Flag successfully!
\nNote: The function solve_rsa_primes
calls math.isqrt
to compute the integer square root of the given integer. This is indispensable! If it is written incorrectly with math.sqrt
, the following overflow error will occur
>>> |
This error happens because math.sqrt
uses floating-point arithmetic but fails to convert large integers to floating-point numbers.
The conventional solution to this problem has to solve a quadratic equation, so the integer square root operation is essential. Is there a solution that doesn't need a square root operation? The answer is yes.
\nIn the original RSA paper, the public exponent \\(e\\) and the private exponent \\(d\\) have the relationship as the following equation
\n\\[d⋅e≡1\\pmod{\\varphi(n)}\\]
\nHere the modular is the Euler's totient function \\(\\varphi(n)=(p-1)(q-1)\\). Since \\(\\varphi(N)\\) is always divisible by \\(\\lambda(n)\\), any d
satisfying the above also satisfies \\(d⋅e≡1\\pmod{\\lambda(n)}\\), thus the private exponent is not unique. Although the calculated \\(d>\\lambda(n)\\), the square root operation can be avoided when applied to the Sum-O-Primes problem. This is because \\[\n\\begin{aligned}\n\\varphi(n)&=(p-1)(q-1)\\\\\n&=pq-(p+q)+1\\\\\n&=n-x+1\n\\end{aligned}\n\\]
Hereby the formula for computing the private exponent becomes
\n\\[\n\\begin{aligned}\nd&≡e^{-1}\\pmod{\\varphi(n)}\\\\\n&≡e^{-1}\\pmod{(n-x+1)}\n\\end{aligned}\n\\]
\nNow that \\(n\\) and \\(x\\) are readily available, this method does not require finding \\(p\\) and \\(q\\) first, and naturally, there is no need for a square root operation. The Python code for this new solution is very concise
\nd1 = pow(e, -1, n - x + 1) |
To compare these two solutions, 4 lines of print and assert statements are added at the end. The execution result of this code is
\n>>> |
As shown above, this solution also succeeds in capturing the flag. The \\(d\\) value (d1
) calculated by the new solution is more than 7 times that of the conventional solution.
Click here to download all the code of this article: Sum-O-Primes.py.gz
\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming","CTF"]},{"title":"Notes on Using uClibc Standard Library in Embedded Linux System","url":"/en/2023/03/10/uClibc-tips/","content":"uClibc is a small and exquisite C standard library for embedded Linux systems. It is widely used in the development of low-end embedded systems and Internet of Things devices. Here are some recent experiences to provide convenience for engineers who need to solve similar problems or meet corresponding requirements.
\nLow-level programming is good for the programmer's soul.
— John Carmack (American computer programmer and video game developer, co-founder of the video game company id Software)
uClibc (sometimes written as μClibc) is a small C standard library designed to provide support for embedded systems and mobile devices using operating systems based on the Linux kernel. uClibc was originally developed to support μClinux, a version of Linux not requiring a memory management unit thus especially suited for microcontroller systems. The \"uC\" in its name is the abbreviation of microcontroller in English, where \"u\" is a Latin script typographical approximation of the Greek letter μ that stands for \"micro\".
\nuClibc is a free and open-source software licensed under the GNU Lesser GPL, and its library functions encapsulate the system calls of the Linux kernel. It can run on standard or MMU-less Linux systems and supports many processors such as i386, x86-64, ARM, MIPS, and PowerPC. Development of uClibc started in 1999 and was written mostly from scratch, but also absorbed code from glibc and other projects. uClibc is much smaller than glibc. While glibc aims to fully support all relevant C standards on a wide range of hardware and kernel platforms, uClibc focuses on embedded Linux systems. It also allows developers to enable or disable some features according to the memory space design requirements.
\nThe following records show the list of C standard library files in two similar embedded systems. The first uses glibc-2.23 version, and the second integrates uClibc-0.9.33.2 version. The total size of glibc library files is more than 2MB, while the uClibc library files add up to less than 1MB. It can be seen that using uClibc does save a lot of storage space.
\nSTM1:/# find . -name "*lib*2.23*" | xargs ls -alh |
With the steady growth of IPv6 deployment, adding IPv6 protocol stack support for embedded systems has become necessary. In a software project that adds IPv4/IPv6 dual-stack function to devices using uClibc, it is found that there is an application link error - undefined reference to getifaddrs
. getifaddrs()
is a very useful function, we can call it to get the address information of all the network interfaces of the system. Query the Linux programming manual:
SYNOPSIS |
The last sentence above is key: only kernels supporting netlink can support address families other than IPv4. The Linux kernel version running on this system is 3.x, which supports netlink. So, could there be a problem with uClibc's support for netlink that causes getifaddrs() not to get compiled?
\nWith this question in mind, search the source code directory of uClibc and find the C file that implements the function getifaddrs()
:
... |
Just as expected! The implementation of the entire function and the definition of the associated data structure ifaddrs_storageare are placed under three nested conditional compilation directives with macros defined as
\nTherefore, as long as their corresponding configuration lines are opened, the problem should be solved. After changing the configuration file of uClibc as follows, rebuild the dynamic link library of uClibc, then the application can be made successfully:
\n--- a/toolchain/uClibc/config-0.9.33.2/common |
Embedded systems often need to provide remote SSH login services for system administrators, which requires the creation of system users and their passwords. Linux saves the user name and the hashed password in the /etc/shadow file. The storage format of the hash value follows a de facto standard called the Modular Crypt Format (MCF for short), and its format is as follows:
\n$<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
Here
\nWith the rapid increase of computing power following Moore's Law, the previously commonly used MD5-based hashing scheme has become obsolete because it is too vulnerable to attack. Newly designed systems are now switched to the SHA-512 hashing scheme, corresponding to $6$
seen in the /etc/shadow file.
Both generation and verification of user password hash values can be implemented with the POSIX C library function named crypt
. This function is defined as follows:
char *crypt(const char *key, const char *salt) |
The input parameter key
points to the string containing the user's password, and salt
points to a string in the format $<id>$<salt>
indicating the hash algorithm and salt to be used. Most Linux distributions use the crypt
function provided by the glibc library. The following figure summarizes the augmented crypt
function in Glibc:
In an embedded Linux system integrating uClibc, uClibc provides support for the crypt
function. But the test found that it returned a null pointer for the correct \\(6\\)
The answer lies in the uClibc's implementation of the crypt
function. Find the corresponding C source code:
|
Aha! It turns out that it only does MD5 hashing by default, and the codes of SHA-256 and SHA-512 need their own conditional compilation macro definitions. This is easy to handle, just edit the configuration file of uClibc and open the latter two.
\n--- a/toolchain/uClibc/config-0.9.33.2/common |
Finally, take a look at the program that comes with uClibc to test the SHA-512 hash algorithm. It clearly lists the data structures defined by the test code, including the salt, the input password, and the expected output, as well as several test vectors:
\nstatic const struct |
It can be seen that the last test case defines the round value 10 ($6$rounds=10$roundstoolow
), while the output shows that the round is 1000 (rounds=1000
). This confirms that the crypt
function implementation of uClibc matches the augmented function of Glibc - in order to ensure security, if the input specified round is too small, crypt
will automatically set to the minimum round of 1000.
In early May 2022, Nozomi Networks, a company focused on providing security solutions for industrial and critical infrastructure environments, released a newly discovered uClibc security vulnerability CVE-2022-30295. This vulnerability exists in the Domain Name System (DNS) implementation of all versions of uClibc and its fork uClibc-ng (prior to version 1.0.41). Since the implementation uses predictable transaction IDs when making DNS requests, there is a risk of DNS cache poisoning attacks.
\nSpecifically, applications often call gethostbyname
library functions to resolve a network address for a given hostname. uClibc/uClibc-ng internally implements a __dns_lookup
function for the actual DNS domain name request and response processing. Taking the last version 0.9.33.2 of uClibc as an example, the screenshot below shows the problematic code in the function __dns_lookup
:
Referring to line 1308, at the first DNS request, the variable local_id
is initialized to the transaction ID value of the last DNS request (stored in a static variable last_id
). Line 1319 is the actual culprit, it simply updates the old local_id
value by incrementing it by 1. This new value is stored back into the variable last_id
, as shown on line 1322. Finally, on line 1334, the value of local_id
is copied into the structure variable h
, which represents the actual content of the DNS request header. This code works pretty much in all available versions of uClibc and uClibc-ng prior to version 1.0.41.
This implementation makes the transaction ID in the DNS request predictable, because the attacker can estimate the value of the transaction ID in the next request as long as he/she detects the current transaction ID. By exploiting this vulnerability, an attacker can disrupt/poison the host's DNS cache by crafting a DNS response containing the correct source port and winning the competition with the legitimate response returned by the DNS server, making the network data of the application in the host system be directed to a trap site set by the attacker.
\nThe maintainers of uClibc-ng responded quickly to the announcement of this security vulnerability. They submitted a fix in mid-May 2022, and released version 1.0.41 including this patch at the end of that month. For uClibc, since this C standard library has stopped releasing any new versions since 2012, it is currently in an unmaintained state, so system R&D engineers need to come up with their repair. The following uClibc patches are available for reference:
\ndiff --git a/libc/inet/resolv.c b/libc/inet/resolv.c |
This uClibc patch is a simplified version of the uClibc-ng official patch. Its core is to read a double-byte random number from the system /dev/urandom
file, and then use it to set the original local_id
, the transaction ID of the DNS request. /dev/urandom
is a special device file of the Linux system. It can be used as a non-blocking random number generator, which will reuse the data in the entropy pool to generate pseudo-random data.
Note that in the above patch, the function dnsrand_setup
must first check urand_fd
whether it is positive, and only open /dev/urandom
when it is not true. Otherwise, the file will be reopened every time the application does a DNS lookup, the system will quickly hit the maximum number of file descriptors allowed, and the system will crash because it cannot open any more files.
Finally, a comparison of an embedded system using uClibc before and after adding DNS security patches is given. The following are the DNS packets intercepted by two sniffers. In the first unpatched system, the transaction ID of the DNS request is incremented in sequence, which is an obvious security hole; the second is after the patch is added, the transaction ID of each DNS request is a random value, and the loophole has been filled.
\n
Memory access errors are the most common software errors that often cause program crashes. The AddressSanitizer tool, developed by Google engineers in 2012, has become the first choice of C/C++ programmers for its wide coverage, high efficiency, and low overhead. Here is a brief introduction to its principle and usage.
\nOne man's \"magic\" is another man's engineering. \"Supernatural\" is a null word.
— Robert Anson Heinlein (American science fiction author, aeronautical engineer, and naval officer)
The C/C++ language allows programmers to have low-level control over memory, and this direct memory management has made it possible to write efficient application software. However, this has also made memory access errors, including buffer overflows, accesses to freed memory, and memory leaks, a serious problem that must be coped with in program design and implementation. While there are tools and software that provide the ability to detect such errors, their operational efficiency, and functional coverage are often less than ideal.
\nIn 2012, Google engineer Konstantin Serebryany and team members released an open-source memory access error detector for C/C++ programs called AddressSanitizer1. AddressSanitizer (ASan) applies new memory allocation, mapping, and code stubbing techniques to detect almost all memory access errors efficiently. Using the SPEC 2006 benchmark analysis package, ASan runs with an average slowdown of less than 2 and memory consumption of about 2.4 times. In comparison, another well-known detection tool Valgrind has an average slowdown of 20, which makes it almost impossible to put into practice.
\nThe following table summarizes the types of memory access errors that ASan can detect for C/C++ programs:
\nError Type | \nAbbreviation | \nNotes | \n
---|---|---|
heap use after free | \nUAF | \nAccess freed memory (dangling pointer dereference) | \n
heap buffer overflow | \nHeap OOB | \nDynamic allocated memory out-of-bound read/write | \n
heap memory leak | \nHML | \nDynamic allocated memory not freed after use | \n
global buffer overflow | \nGlobal OOB | \nGlobal object out-of-bound read/write | \n
stack use after scope | \nUAS | \nLocal object out-of-scope access | \n
stack use after return | \nUAR | \nLocal object out-of-scope access after return | \n
stack buffer overflow | \nStack OOB | \nLocal object out-of-bound read/write | \n
ASan itself cannot detect heap memory leaks. But when ASan is integrated into the compiler, as it replaces the memory allocation/free functions, the original leak detection feature of the compiler tool is consolidated with ASan. So, adding the ASan option to the compilation command line also turns on the leak detection feature by default.
\nThis covers all common memory access errors except for \"uninitialized memory reads\" (UMR). ASan detects them with a false positive rate of 0, which is quite impressive. In addition, ASan detects several C++-specific memory access errors such as
\nnew foo[n]
, should not call delete foo
for deletion, use delete [] foo
instead.ASan's high reliability and performance have made it the preferred choice of compiler and IDE developers since its introduction. Today ASan is integrated into all four major compilation toolsets:
\nCompiler/IDE | \nFirst Support Version | \nOS | \nPlatform | \n
---|---|---|---|
Clang/LLVM2 | \n3.1 | \nUnix-like | \nCross-platform | \n
GCC | \n4.8 | \nUnix-like | \nCross-platform | \n
Xcode | \n7.0 | \nMac OS X | \nApple products | \n
MSVC | \n16.9 | \nWindows | \nIA-32, x86-64 and ARM | \n
ASan's developers first used the Chromium open-source browser for routine testing and found more than 300 memory access errors over 10 months. After integration into mainstream compilation tools, it reported long-hidden bugs in numerous popular open-source software, such as Mozilla Firefox, Perl, Vim, PHP, and MySQL. Interestingly, ASan also identified some memory access errors in the LLVM and GCC compilers' code. Now, many software companies have added ASan run to their mandatory quality control processes.
\nThe USENIX conference paper 3, published by Serebryany in 2012, comprehensively describes the design principles, algorithmic ideas, and programming implementation of ASan. In terms of the overall structure, ASan consists of two parts.
\nmalloc/free
and its related functions to create poisoned red zones at the edge of dynamically allocated heap memory regions, delay the reuse of memory regions after release, and generate error reports.Here shadow memory, compiler instrumentation, and memory allocation function replacement are all previously available techniques, so how has ASan innovatively applied them for efficient error detection? Let's take a look at the details.
\nMany inspection tools use separated shadow memory to record metadata about program memory, and then apply instrumentation to check the shadow memory during memory accesses to confirm that reads and writes are safe. The difference is that ASan uses a more efficient direct mapping shadow memory.
\nThe designers of ASan noted that typically the malloc
function returns a memory address that is at least 8-byte aligned. For example, a request for 20 bytes of memory would divide 24 bytes of memory, with the last 3 bits of the actual return pointer being all zeros. in addition, any aligned 8-byte sequence would only have 9 different states: the first \\(k\\,(0\\leq k \\leq 8)\\) bytes are accessible, and the last \\(8-k\\) are not. From this, they came up with a more compact shadow memory mapping and usage scheme:
Shadow = (Mem >> 3) + 0x20000000;
Shadow = (Mem >> 3) + 0x7fff8000;
The following figure shows the address space layout and mapping relationship of ASan. Pay attention to the Bad area in the middle, which is the address segment after the shadow memory itself is mapped. Because shadow memory is not visible to the application, ASan uses a page protection mechanism to make it inaccessible.
\nOnce the shadow memory design is determined, the implementation of compiler instrumentation to detect dynamic memory access errors is easy. For memory accesses of 8 bytes, the shadow memory bytes are checked by inserting instructions before the original read/write code, and an error is reported if they are not zero. For memory accesses of less than 8 bytes, the instrumentation is a bit more complicated, where the shadow memory byte values are compared with the last three bits of the read/write address. This situation is also known as the \"slow path\" and the sample code is as follows.
\n// Check the cases where we access first k bytes of the qword |
For global and stack (local) objects, ASan has designed different instrumentation to detect their out-of-bounds access errors. The red zone around a global object is added by the compiler at compile time and its address is passed to the runtime library at application startup, where the runtime library function then poisons the red zone and writes down the address needed in error reporting. The stack object is created at function call time, and accordingly, its red zone is created and poisoned at runtime. In addition, because the stack object is deleted when the function returns, the instrumentation code must also zero out the shadow memory it is mapped to.
\nIn practice, the ASan compiler instrumentation process is placed at the end of the compiler optimization pipeline so that instrumentation only applies to the remaining memory access instructions after variable and loop optimization. In the latest GCC distribution, the ASan compiler stubbing code is located in two files in the gcc subdirectory gcc/asan.[ch]
.
The runtime library needs to include code to manage shadow memory. The address segment to which shadow memory itself is mapped is to be initialized at application startup to disable access to shadow memory by other parts of the program. The runtime library replaces the old memory allocation and free functions and also adds some error reporting functions such as __asan_report_load8
.
The newly replaced memory allocation function malloc
will allocate additional storage as a red zone before and after the requested memory block and set the red zone to be non-addressable. This is called the poisoning process. In practice, because the memory allocator maintains a list of available memory corresponding to different object sizes, if the list of a certain object is empty, the OS will allocate a large set of memory blocks and their red zones at once. As a result, the red zones of the preceding and following memory blocks will be connected, as shown in the following figure, where \\(n\\) memory blocks require only \\(n+1\\) red zones to be allocated.
The new free
function needs to poison the entire storage area and place it in a quarantine queue after the memory is freed. This prevents the memory region from being allocated any time soon. Otherwise, if the memory region is reused immediately, there is no way to detect incorrect accesses to the recently freed memory. The size of the quarantine queue determines how long the memory region is in quarantine, and the larger it is the better its capability of detecting UAF errors!
By default, both the malloc
and free
functions log their call stacks to provide more detailed information in the error reports. The call stack for malloc
is kept in the red zone to the left of the allocated memory, so a large red zone can retain more call stack frames. The call stack for free
is stored at the beginning of the allocated memory region itself.
Integrated into the GCC compiler, the source code for the ASan runtime library replacement is located in the libsanitizer subdirectory libsanitizer/asan/*
, and the resulting runtime library is compiled as libasan.so
.
ASan is very easy to use. The following is an example of an Ubuntu Linux 20.4 + GCC 9.3.0 system running on an x86_64 virtual machine to demonstrate the ability to detect various memory access errors.
\nAs shown below, the test program writes seven functions, each introducing a different error type. The function names are cross-referenced with the error types one by one:
\n/* |
The test program calls the getopt
library function to support a single-letter command line option that allows the user to select the type of error to be tested. The command line option usage information is as follows.
\b$ ./asan-test |
The GCC compile command for the test program is simple, just add two compile options
\n-fsanitize=address
: activates the ASan tool-g
: enable debugging and keep debugging informationFor Heap OOB error, the run result is
\n$ ./asan-test -b |
Referring to the heap-buffer-overflow
function implementation, you can see that it requests 40 bytes of memory to hold 10 32-bit integers. However, on the return of the function, the code overruns to read the data after the allocated memory. As the above run log shows, the program detects a Heap OOB error and aborts immediately. ASan reports the name of the source file and line number asan-test.c:34
where the error occurred, and also accurately lists the original allocation function call stack for dynamically allocated memory. The \"SUMMARY\" section of the report also prints the shadow memory data corresponding to the address in question (observe the lines marked by =>
). The address to be read is 0x604000000038, whose mapped shadow memory address 0x0c087fff8007 holds the negative value 0xfa (poisoned and not addressable). Because of this, ASan reports an error and aborts the program.
The Stack OOB test case is shown below. ASan reports an out-of-bounds read error for a local object. Since the local variables are located in the stack space, the starting line number asan-test.c:37
of the function stack_buffr_overflow
is listed. Unlike the Heap OOB report, the shadow memory poisoning values for the front and back redzone of the local variable are different, with the previous Stack left redzone
being 0xf1 and the later Stack right redzone
being 0xf3. Using different poisoning values (both negative after 0x80) helps to quickly distinguish between the different error types.
$ ./asan-test -s |
The following Global OOB test result also clearly shows the error line asan-test.c:16
, the global variable name ga
and its definition code location asan-test.c:13:5
, and you can also see that the global object has a red zone poisoning value of 0xf9.
$ ./asan-test -o |
Note that in this example, the global array int ga[10] = {1};
is initialized, what happens if it is uninitialized? Change the code slightly
int ga[10]; |
Surprisingly, ASan does not report the obvious Global OOB error here. Why?
\nThe reason has to do with the way GCC treats global variables. The compiler treats functions and initialized variables as Strong symbols, while uninitialized variables are Weak symbols by default. Since the definition of weak symbols may vary from source file to source file, the size of the space required is unknown. The compiler cannot allocate space for weak symbols in the BSS segment, so it uses the COMMON block mechanism so that all weak symbols share a COMMON memory region, thus ASan cannot insert the red zone. During the linking process, after the linker reads all the input target files, it can determine the size of the weak symbols and allocate space for them in the BSS segment of the final output file.
\nFortunately, GCC's -fno-common
option turns off the COMMON block mechanism, allowing the compiler to add all uninitialized global variables directly to the BSS segment of the target file, also allowing ASan to work properly. This option also disables the linker from merging weak symbols, so the linker reports an error directly when it finds a compiled unit with duplicate global variables defined in the target file.
This is confirmed by a real test. Modify the GCC command line for the previous code segment
\ngcc asan-test.c -o asan-test -fsanitize=address -fno-common -g |
then compile, link, and run. ASan successfully reported the Global OOB error.
\nThe following is a running record of UAF error detection. Not only is the information about the code that went wrong reported here, but also the call stack of the original allocation and free functions of the dynamic memory is given. The log shows that the memory was allocated by asan-test.c:25
, freed at asan-test.c:27
, and yet read at asan-test.c:28
. The shadow memory data printed later indicates that the data filled is negative 0xfd, which is also the result of the poisoning of the memory after it is freed.
$ \u0007./asan-test -\bf |
The results of the memory leak test are as follows. Unlike the other test cases, ABORTING
is not printed at the end of the output record. This is because, by default, ASan only generates a memory leak report when the program terminates (process ends). If you want to check for leaks on the fly, you can call ASan's library function __lsan_do_recoverable_leak_check
, whose definition is located in the header file sanitizer/lsan_interface.h
.
$ ./asan-test -l |
See the stack_use_after_scope
function code, where the memory unit holding the local variable c
is written outside of its scope. The test log accurately reports the line number line 54
where the variable is defined and the location of the incorrect writing code asan-test.c:57
:
./asan-test -\bp |
The UAR test has its peculiarities. Because the stack memory of a function is reused immediately after it returns, to detect local object access errors after return, a \"pseudo-stack\" of dynamic memory allocation must be set up, for details check the relevant Wiki page of ASan4. Since this algorithm change has some performance impact, ASan does not detect UAR errors by default. If you really need to, you can set the environment variable ASAN_OPTIONS
to detect_stack_use_after_return=1
before running. The corresponding test logs are as follows.
$ export ASAN_OPTIONS=detect_stack_use_after_return=1 |
ASan supports many other compiler flags and runtime environment variable options to control and tune the functionality and scope of the tests. For those interested please refer to the ASan flags Wiki page5.
\nA zip archive of the complete test program is available for download here: asan-test.c.gz
\nSerebryany, K.; Bruening, D.; Potapenko, A.; Vyukov, D. \"AddressSanitizer: a fast address sanity checker\". In USENIX ATC, 2012↩︎
Here is a series of general study guides to college-level C programming courses. This is the first part covering compilation and linking, file operations, typedef, structures, string operations, basic pointer operations, etc.
\nWrite the command to compile a single C file named \"hello.c\" into an object file called \"hello.o\".
\ngcc -c hello.c -o hello.o
Write the command to link two object files named \"hello.o\" and \"goodbye.o\" into the executable called \"application\".
\ngcc hello.o goodbye.o -o application
Can you \"run\" an object file if it contains the \"main()\" function?
\nNo, an object file cannot be run directly. If you force it to run, it will exec format error
.
Can you \"run\" an executable that contains a single function called \"main()\"?
\nYes, an executable with just main() can be run.
Can you \"run\" an executable that does not contain a function called \"main()\"?
\nNo, main() is required to run an executable.
What does the \"-Wall\" flag do?
\n\"-Wall\" enables all compiler warnings
What does the \"-g\" flag do?
\n\"-g\" adds debugging information.
What does the \"-ansi\" flag do?
\n\"-ansi\" enables strict ANSI C mode. The \"-ansi\" flag is equivalent to the -\"std=c89\" flag.
What does the \"-c\" flag do?
\n\"-c\" compiles to object file only, does not link.
What does the \"-o\" flag do?
\n\"-o\" specifies output file name.
\nGiven the following FILE pointer variable definition, write the code that will open a file named \"hello.txt\" for read-only access and print a message of your choice if there was an error in doing so.
\nFILE *my_file = 0;
my_file = fopen("hello.txt", "r");
if (my_file = NULL) {
fprintf(stdout, "Failed to open the file\\n");
}
Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for read access. Put the code inside the 'if' predicate:
\nif (access("hello.txt", R_OK) == 0) {
/* Yes, we can open the file... */
}
Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for write access. Put the code inside the 'if' predicate:
\nif (access("hello.txt", W_OK) == 0) {
/* Yes, we can open the file... */
}
Write a function called read_and_print() that will do the following:
\nint read_and_print() {
char my_string[100];
my_int;
FILE *fp = fopen("hello.txt", "r");
if(!fp) return -1;
if (fscanf(fp, "%s", my_string) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
if (fscanf(fp, "%d", &my_int) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
printf("%s %d\\n", my_string, my_int);
fclose(fp);
fp = NULL;
return my_int;
}
Write a function named print_reverse that will open a text file named \"hello.txt\" and print each character in the file in reverse. i.e. print the first character last and the last character first. The function should return the number of characters in the file. Upon any error, return -1. HINT: Use fseek() a lot to do this.
\nint print_reverse(char* filename) {
FILE* fp = fopen(filename, "r");
if(!fp) return -1;
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
for (int i = size - 1; i >= 0; i--) {
fseek(fp, i, SEEK_SET);
char c = fgetc(fp);
printf("%c", c);
}
fclose(fp);
fp = NULL;
return size;
}
Write a function that defines a structure, initializes it, writes it to a file called \"struct.out\", closes the file, re-opens the file for read-only access, reads a single structure into a new struct variable, and then closes the file. Print the structure contents to the screen. On any error, return -1. Otherwise, return 0.
\n
struct Person {
char name[50];
int age;
};
int write_and_read_struct() {
struct Person p = { "John Doe", 30 };
// Write struct to file
FILE* fp = fopen("struct.out", "w");
if (!fp) return -1;
if (fwrite(&p, sizeof(struct Person), 1, fp) != 1) {
\tfclose(fp);
\tfp = NULL:
\treturn -1;
}
fclose(fp);
// Read struct from file
fp = fopen("struct.out", "r");
if (!fp) return -1;
struct Person p2;
if (fread(&p2, sizeof(struct Person), 1, fp) != 1) {
fclose(fp);
fp = NULL;
return -1;
}
fclose(fp);
fp = NULL;
// Print struct
printf("Name: %s, Age: %d\\n", p2.name, p2,age);
return 0;
}
Declare a type called \"my_array_t\" that is an array of 15 floats.
\ntypedef float my_array_t[15];
Declare a type called \"struct_arr_t\" that is an array of 10 structs of the format
\nstruct str {
int x;
int y;
};
typedef struct str struct_arr_t[10];
Define a variable called my_str_arr of type struct_arr_type.
\nstruct_arr_t my_str_arr;
Can two elements within a structure have the same name?
\nNo, two elements cannot have the same name
Can you initialize a structure like this?
\nstruct my_str {
int x;
float y;
} mine = { 0, 0.0 };
Can you initialize a structure like this?
\nstruct my_str {
int x;
float y;
};
void my_func(int n) {
my_str mine = { n, 0.0 };
}struct str mine = { n, 0.0 };
instead.
Declare a structure that contains an integer element named i, a floating point element named f, and an array of 20 characters named str (in that order). Name it anything you want.
\nstruct mystruct {
int i;
float f;
char str[20];
};
Define a variable called \"my_new_struct\" of the type in the previous question.
\nstruct mystruct my_new_struct;
Define a variable called \"my_array_of_structs\" that is an array of 40 structures of the type in the prior two questions.
\nstruct mystruct my_array_of_structs[40];
Define a function called bigger_rectangle() that will accept one argument of the structure type rectangle (declared below) and will multiply the width dimension by 1.5, the height dimension by 2.5 and the length dimension by 3. The function should return the new structure. Define a temporary local variable if you want to.
\nstruct rectangle {
float height;
float width;
float length;
};
struct rectangle bigger_rectangle(struct rectangle r) {
struct rectangle bigger;
bigger.height = r.height * 2.5;
bigger.width = r.width * 1.5;
bigger.length = r.length * 3;
return bigger;
}
Write a function named sum_rectangles that will open a binary file named \"rect.in\" for reading and read the binary images of rectangle structures from it. For each rectangle structure, add its elements to those of the first structure read. e.g. sum the height fields of all the structures, sum the width fields of all the structures, etc... Return a structure from sum_rectangles where each element represents the sum of all structures read from the file. i.e. the height field should be the sum of all of the height fields of each of the structures. On any file error, return the structure { -1.0, -1.0, -1.0 }.
\n
struct rectangle {
float height;
float width;
float length;
};
struct rectangle sum_rectangles() {
struct rectangle bad_struct = {-1.0, -1.0, -1.0};
FILE *fp = fopen("rect.in", "rb");
if(!fp) {
return bad_struct;
}
struct rectangle sum = {0, 0, 0};
struct rectangle r;
if (fread(&r, sizeof(struct rectangle), 1, fp) != 1) {
fclose(fp);
fp = NULL;
return bad_struct;
}
sum.height = r.height;
sum.width = r.width;
sum.length = r.length;
while (fread(&r, sizeof(struct rectangle), 1, fp) == 1) {
sum.height += r.height;
sum.width += r.width;
sum.length += r.length;
}
fclose(fp);
fp = NULL;
return sum;
}
Under what circumstances would you place an assert() into your code?
\nUsed to check for logical errors and malformed data.
What will be the result of the following code:
\nint my_func() {
int count = 0;
int sum = 0;
for (count = 0; count < 100; count++) {
assert(sum > 0);
sum = sum + count;
}
return sum;
}
What might you do to the previous code to make it do a \"better\" job?
\nMove assert(sum > 0);
down, after for loop. Or change to assert(sum >= 0);
Write a function called do_compare() that will prompt the user for two strings of maximum length 100. It should compare them and print one of the following messages:
\nThe function should always return zero.
\n
int do_compare() {
char str1[101], str2[101];
// Prompt the user to enter two strings
printf("Enter the first string (up to 100 characters): ");
fgets(str1, sizeof(str1), stdin);
printf("Enter the second string (up to 100 characters): ");
fgets(str2, sizeof(str2), stdin);
// Compare the strings
int cmp = strcmp(str1, str2);
// Print the comparison result
if (cmp == 0) {
printf("The strings are equal.\\n");
} else if (cmp < 0) {
printf("The first string comes before the second.\\n");
} else {
printf("The second string comes before the first.\\n");
}
return 0;
}
What is the difference between initialization of a variable and assignment to a variable?
\nInitialization is giving a variable its initial value, typically at the time of declaration, while assignment is giving a new value to an already declared variable at any point after initialization.
What is the difference between a declaration and a definition?
\nDeclaration is announcing the properties of var (no memory allocation), definition is allocating storage for a var and initializing it.
What is the difference between a global variable and a local variable?
\nGlobal variables have a broader scope, longer lifetime, and higher visibility compared to local variables, which are limited to the scope of the function in which they are declared.
For the following questions, assume that the size of an 'int' is 4 bytes, the size of a 'char' is one byte, the size of a 'float' is 4 bytes, and the size of a 'double' is 8 bytes. Write the size of the following expressions:
\nstruct my_coord {
int x;
int y;
double altitude;
};
struct my_line {
struct my_coord first;
struct my_coord second;
char name[10];
};
struct my_coord var;
struct my_coord array[3];
struct my_line one_line;
struct my_line two_lines[2];
sizeof(struct my_coord) = __16___
\nsizeof(var) = __16___
\nsizeof(array[1]) = __16___
\nsizeof(array[2]) = __16___
\nsizeof(array) = __48___
\nsizeof(struct my_line) = __48___
\nsizeof(two_lines) = __96___
\nsizeof(one_line) = __48___
\nExplanation: When calculating the size of a struct, we need to consider alignment and padding, which can affect the overall size of the struct. In the case of struct my_line
, the total size is influenced by the alignment requirements of its members. The largest member of struct my_coord
is double altitude
, which is 8 bytes. This means that the double altitude
member will determine the alignment and padding for the entire struct my_coord
within struct my_line
.
So here char name[10];
will occupy (10 bytes) + (6 bytes padding to align char[10] on an 8-byte boundary). This ends up with (16+16+10+6) for the size of struct my_line
.
Remember that the size of the structure should be a multiple of the biggest variable.
Draw the memory layout of the prior four variables; var, array, one_line, and two_lines on a line of boxes. Label the start of each variable and clearly show how many bytes each element within each structure variable consumes.
Re-define the two_lines variable above and _initialize_ it's contents with the following values:
\nfirst my_line structure:
first my_coord structure:
x = 1
y = 3
altitude = 5.6
second my_coord structure:
x = 4
y = 5
altitude = 2.1
name = "My Town"
second my_line structure:
first my_coord structure:
x = 9
y = 2
altitude = 1.1
second my_coord structure:
x = 3
y = 3
altitude = 0.1
name = "Your Town"
struct my_line two_lines[2] = {
{
{1, 3, 5.6},
{4, 5, 2.1},
"My Town"
},
{
{9, 2, 1.1},
{3, 3, 0.1},
"Your Town"
}
};
How many bytes large is the following definition?
\nstruct my_coord new_array[] = { |
(4 + 4 + 8) * 3 = 48
What is printed by the following three pieces of code:
\nint x = 0; int x = 0; int x = 0;
int y = 0; int y = 0; int y = 0;
int *p = NULL; int *p = NULL; int *p = NULL;
int *q = NULL; int *q = NULL;
p = &x;
*p = 5; p = &x; p = &y;
p = &y; q = p; q = &x;
*p = 7; *q = 7; p = 2;
q = 3;
printf("%d %d\\n", x, y); printf("%d %d\\n", x, y); printf("%d %d\\n", x, y);
The 1st column code snippet printed 5 7
. The 1st column code snippet printed 7 0
. The 1st column code snippet printed 0 0
.
Consider the following variable definitions:
\nint x = 2; |
And assume that p is initialized to point to one of the integers in arr. Which of the following statements are legitimate? Why or why not?
\np = arr; arr = p; p = &arr[2]; p = arr[x]; p = &arr[x];
arr[x] = p; arr[p] = x; &arr[x] = p; p = &arr; x = *arr;
x = arr + x; p = arr + x; arr = p + x; x = &(arr+x); p++;
x = --p; x = *p++; x = (*p)++; arr++; x = p - arr;
x = (p>arr); arr[*p]=*p; *p++ = x; p = p + 1; arr = arr + 1;
Let's go through each statement to determine if it is legitimate or not, and explain:
\np = arr;
- Legitimate. Assigns the address of the first element of arr
to p
.arr = p;
- Not legitimate. You cannot assign to an array name.p = &arr[2];
- Legitimate. Assigns the address of arr[2]
to p
.p = arr[x];
- Not legitimate. arr[x]
is an integer value, not an address.p = &arr[x];
- Legitimate. Assigns the address of arr[x]
to p
.arr[x] = p;
- Not legitimate. arr[x]
is an integer value, not a pointer.arr[p] = x;
- Not legitimate. arr[p]
is not a valid operation. p
should be an index, not a pointer.&arr[x] = p;
- Not legitimate. You cannot assign a value to the address of an element.p = &arr;
- Not legitimate. &arr
is the address of the whole array, not a pointer to an integer.x = *arr;
- Legitimate. Assigns the value of the first element of arr
to x
.x = arr + x;
- Legitimate. Calculates the address of arr[x]
and assigns it to x
.p = arr + x;
- Legitimate. Calculates the address of arr[x]
and assigns it to p
.arr = p + x;
- Not legitimate. You cannot assign to an array name.x = &(arr+x);
- Not legitimate. &
expects an lvalue, but (arr+x)
is not an lvalue.p++;
- Legitimate. Increments the pointer p
to point to the next element.x = --p;
- Legitimate. Decrements p
and assigns its value to x
.x = *p++;
- Legitimate. Assigns the value pointed to by p
to x
, then increments p
.x = (*p)++;
- Legitimate. Assigns the value pointed to by p
to x
, then increments the value pointed to by p
.arr++;
- Not legitimate. You cannot increment the entire array arr
.x = p - arr;
- Legitimate. Calculates the difference in addresses between p
and arr
and assigns it to x
.x = (p>arr);
- Not legitimate. Comparison between a pointer and an array is not valid.arr[*p]=*p;
- Not legitimate. arr[*p]
is not a valid assignment target.*p++ = x;
- Legitimate. Assigns x
to the value pointed to by p
, then increments p
.p = p + 1;
- Legitimate. Increments the pointer p
to point to the next memory location.arr = arr + 1;
- Not legitimate. You cannot increment the entire array arr
.📝Notes: The difference between x = *p++;
and x = (*p)++;
lies in how the increment operator (++) is applied.
x = *p++;
This statement first dereferences the pointer p to get the value it points to, assigns that value to x and then increments the pointer p to point to the next element (not the value pointed to by p). So, x gets the value pointed to by p before the increment.x = (*p)++;
This statement first dereferences the pointer p to get the value it points to, assigns that value to x, and then increments the value pointed to by p. So, x gets the value pointed to by p before the increment, and the value at the memory location pointed to by p is incremented.Here's a brief example to illustrate the difference:
\n
int main() {
int array[] = {1, 2, 3};
int *p = array;
int x;
// x gets the value pointed to by p, then p is incremented
x = *p++; // x = 1, p now points to array[1]
printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
// x gets the value pointed to by p, then the value pointed to
// by p is incremented
x = (*p)++; // x = 2, array[1] is now 3
printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
return 0;
}
The output of the above program is
\nx = 1, array[1] = 2, p points to 2
x = 2, array[1] = 3, p points to 3
To test your understanding, now check the following code snippet, what will the output be:
\nint x = 2, y = 15, z = 0;
int *p = 0;
p = &y;
x = *p++;
printf("x = %d, y = %d, z = %d\\n", x, y, z);
p = &y;
z = (*p)++;
printf("x = %d, y = %d, z = %d\\n", x, y, z);
Answer So the variable y has its value incremented after
\nx = 15, y = 15, z = 0
x = 15, y = 16, z = 15z = (*p)++;
.
Given the following definitions:
\nint arr[] = { 0, 1, 2, 3 };
int *p = arr;
p = p + 1;
p++;
Yes, the two statements p = p + 1;
and p++;
are equivalent in this context. Both statements increment the pointer p to point to the next element in the array arr.
In general, if ptr is a pointer to type T, then ptr + n
will point to the memory location \"ptr + n * sizeof(T)\". This is useful for iterating over arrays or accessing elements in memory sequentially.
Write a function called 'swap' that will accept two pointers to integers and will exchange the contents of those integer locations.
\nShow a call to this subroutine to exchange two variables.
\nHere is the sample code:
\n
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
printf("Before: x = %d, y = %d\\n", x, y);
swap(&x, &y);
printf("After: x = %d, y = %d\\n", x, y);
return 0;
}
Why is it necessary to pass pointers to the integers instead of just passing the integers to the Swap subroutine?
\nIt is necessary to pass pointers to the integers instead of just passing the integers themselves to the swap subroutine because C passes arguments by value. When you pass an integer to a function, a copy of the integer's value is made and passed to the function. Any changes made to the parameter inside the function do not affect the original variable outside the function.
\nBy passing pointers to integers (int *a
and int *b
), you are passing the memory addresses of the integers. This allows the swap function to access and modify the actual integers in memory, rather than working with copies. As a result, the values of the integers are swapped correctly, and the changes are reflected outside the function.
In summary, passing pointers to integers allows the swap function to modify the values of the integers themselves, rather than just copies of the values.
What would happen if you called swap like this:
\nint x = 5;
swap(&x, &x);
If you called swap(&x, &x);
with the same pointer &x
for both arguments, it would effectively try to swap the contents of x with itself. The result would be that x would remain unchanged, as the swap operation would effectively cancel itself out. The swap operation had no net effect on x.
Can you do this: (why or why not?)
\nswap(&123, &456);
What does the following code print:
\nint func() { |
The output is
\n3 |
Explanation:
\narray[2]
which is 9.p++
, p points to array[3]
which is 3. The value 3 is printed.*(--p) = 7;
sets array[3]
to 7.(*p)++;
increments the value at array[3]
(which is now 7) to 8.4 2 9 8 8
.Write a subroutine called clear_it that accepts a pointer to integer and an integer that indicates the size of the space that the pointer points to. clear_it should set all of the elements that the pointer points to to zero.
\nvoid clear_it(int *ptr, int size) {
for (int i = 0; i < size; i++) {
*(ptr + i) = 0;
}
}
Write a subroutine called add_vectors that accepts three pointers to integer and a fourth parameter to indicate the size of the spaces that the pointers point to. add_vectors should add the elements of the first two 'vectors' together and store them in the third 'vector'. e.g. if two arrays of 10 integers, A and B, were to be added together and the result stored in an array C of the same size, the call would look like add_vectors(a, b, c, 10);
and, as a result, c[5] would be the sum of a[5] and b[5]
All four implementations below are equivalent solutions to this problem:
\nvoid add_vectors(int *a, int *b, int *c, int size) {
for (int i = 0; i < size; i++) {
c[i] = a[i] + b[i];
}
}
void add_vectors1(int *a, int *b, int *c, int size) {
int *end = c + size;
while (c < end) {
*c++ = *a++ + *b++;
}
}
void add_vectors2(int *a, int *b, int *c, int size) {
for (int i=0; i<size; i++) {
*c++ = *a++ + *b++;
}
}
void add_vectors3(int *a, int *b, int *c, int size) {
for (int i=0; i<size; i++) {
*(c+i) = *(a+i) + *(b+i);
}
}
Here is a series of general study guides to college-level C programming courses. This is the second part covering dynamic memory allocation, advanced pointer operations, recursion, linked list and tree common functions, etc.
\nGiven the following definitions:
\nint *pi = NULL;
float *pf = NULL;
char *pc = NULL;
char my_string[] = "Hello, World!";
write statements to do the following memory operations:
\nreserve space for 100 integers and assign a pointer to that space to pi
\npi = (int *)malloc(sizeof(int) * 100);
assert(pi != NULL);
reserve space for 5 floats and assign a pointer to that space to pf
\npf = (float *)malloc(sizeof(float) * 5);
assert(pf != NULL);
unreserve the space that pi points to
\nfree(pi);
pi = NULL;
reserve space for enough characters to hold the string in my_string and assign a pointer to that space to pc. Copy my_string into that space.
\npc = (char *)malloc(strlen(my_string) + 1));
assert(pc != NULL);
strcpy(pc, mystring);
free everything that hasn't been unreserved yet.
\nfree(pc);
free(pf);
pc = NULL;
pf = NULL;
What happens if you reserve memory and assign it to a pointer named p and then reserve more memory and assign the new pointer to p? How can you refer to the first memory reservation?
\nIf you reserve then assign then reserve more memory you will have a memory leak. If you want to refer to the first pointer, you can set a new pointer to point to the new one before reserving more memory.
Does it make sense to free() something twice? What's a good way to prevent this from happening?
\nNo, it doesn’t make sense to free something twice, a good way to prevent this is setting the thing you freed to NULL after freeing it.
Suppose p is a pointer to a structure and f is one of its fields. What is a simpler way of saying: x = (*p).f;
.
x = p->f;
Given the following declarations and definitions:
\nstruct s {
\tint x;
\tstruct s *next;
};
struct s *p1 = NULL;
struct s *p2 = NULL;
struct s *p3 = NULL;
struct s *p4 = NULL;
struct s *p5 = NULL;
p5 = malloc(sizeof(struct s));
p5->x = 5;
p5->next = NULL;
p4 = malloc(sizeof(struct s));
p4->x = 4;
p4->next = p5;
p3 = malloc(sizeof(struct s));
p3->x = 3;
p3->next = p4;
p2 = malloc(sizeof(struct s));
p2->x = 2;
p2->next = p3;
p1 = malloc(sizeof(struct s));
p1->x = 1;
p1->next = p2;
printf("%d %d\\n", p1->next->next->next->x, p2->next->x);
It will print \"4 3\".
Write a subroutine called do_allocate
that is passed a pointer to the head pointer to a list of block structures: do_allocate(struct block **)
. If the head pointer is NULL, do_allocate
should allocate a new struct block and make the head pointer point to it. If the head is not NULL, the new struct block should be prepended to the list, and the head pointer set to point to it.
This is a linked list insertion function. New data items should always be inserted into the front of the list. Note the input argument has to be a pointer to pointer to make a change to the original head pointer. A sample solution is shown below
\n
struct block {
int data;
struct block *next;
};
void do_allocate(struct block **head) {
struct block *new_block = malloc(sizeof(struct block));
if (new_block == NULL) {
// Handle memory allocation failure
return;
}
// Initialize the new block
new_block->data = 0;
new_block->next = *head;
// Update the head pointer
*head = new_block;
}
Write a subroutine called my_free that will accept a pointer to a pointer of some arbitrary type and:
\n
void my_free(void **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
Given the following declaration:
\nstruct employee {
char *name;
char *title;
int id;
};
struct employee *create_employee(const char *name, const char *title, int id)
{
struct employee *new_employee = malloc(sizeof(struct employee));
if (new_employee == NULL) {
return NULL;
}
// Allocate memory for the name and copy the string
new_employee->name = malloc(strlen(name) + 1);
if (new_employee->name == NULL) {
free(new_employee);
return NULL;
}
strcpy(new_employee->name, name);
// Allocate memory for the title and copy the string
new_employee->title = malloc(strlen(title) + 1);
if (new_employee->title == NULL) {
free(new_employee->name);
free(new_employee);
return NULL;
}
strcpy(new_employee->title, title);
// Set the ID
new_employee->id = id;
return new_employee;
}
Write a subroutine called fire_employee that accepts a pointer to pointer to struct employee, frees its storage and sets the pointer that points to the storage to NULL.
\nvoid fire_employee(struct employee **emp_ptr) {
if (emp_ptr != NULL && *emp_ptr != NULL) {
\tfree((*emp_ptr)->name);
\tfree((*emp_ptr)->title);
\tfree(*emp_ptr);
\t*emp_ptr = NULL;
}
}
Create a recursive function to compute the factorial function.
\nunsigned long long factorial(unsigned int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
Create a recursive function to compute the Nth element of the Fibonacci sequence: 0 1 1 2 3 5 8 13 21 34 55 ...
\nunsigned int fibonacci(unsigned int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Implement a recursive list search. e.g. each function call should either return the list node that it's looking at because it matches the search item or it should return the value from calling itself on the next item in the list.
\nstruct Node {
int data;
struct Node* next;
};
struct Node* search(struct Node* node, int value) {
if (node == NULL) return NULL;
if (node->data == value) {
return node;
} else {
// Recursive call on the next node
return search(node->next, value);
}
}
|
|
Try the following two programs to appreciate the differences between static and non-static local variables.
\nvoid try() { void try() {
int x = 0; static int x = 0;
if (x == 0) { if (x == 0) {
x = 5; x = 5;
} }
x++; x++;
printf("X = %d\\n", x); printf("X = %d\\n", x);
} }
int main() { int main() {
int i=0; int i=0;
for (i=0; i<10; i++) for (i=0; i<10; i++)
try(); try();
} }
// Output "X = 6" always // Output "X = 6/7/8/..."
What happens if you define a global variable with a static storage class in one module and attempt to refer to that variable in a different module?
\nThe variable will not be accessible in the other module. This is because static variables have internal linkage by default, meaning they are only accessible within the same module.
Can a function be declared with a static storage class? If so, how? If not, why not?
\nYes, you can declare a function with the static storage class, you can use the static keyword. It means that the function has internal linkage, which restricts its scope to the current translation unit (i.e., the source file in which it is defined). This means that the function can only be called from within the same source file, and its name is not visible outside of that file.
Create a global variable in one module and, in another module use an \"extern\" declaration to refer to it.
\nint globalVariable = 42;
extern int globalVariable; // Declare the global variable from module1
int main() {
printf("The value of globalVariable is: %d\\n", globalVariable);
return 0;
}
Under what conditions can you qualify a type as \"const\"?
\nThe const keyword is used to indicate that the value of the object with that type cannot be modified.
What is the difference between the following types?
\nconst char * cp1;
char * const cp2;
const char * const cp3;
const char * cp1;
: This declares cp1 as a pointer to a constant char. It means that the data cp1 points to cannot be modified through cp1, but cp1 itself can be changed to point to a different memory location.
char * const cp2;
: This declares cp2 as a constant pointer to a char. It means that cp2 always points to the same memory location, and this memory location cannot be changed. However, the data at this memory location can be modified through cp2.
const char * const cp3;
: This declares cp3 as a constant pointer to a constant char. It means that both cp3 and the data it points to are constant. cp3 cannot be changed to point to a different memory location, and the data it points to cannot be modified through cp3.
In summary:
\nName all of the first-class types in \"C\".
\nScalar types (e.g., int, float, double, char, void, short, long, etc.)
Give an example of a derived type in \"C\".
\nPointer types (e.g., int *
, char *
, etc.).
\nPointer to function types (e.g., int (*)(int, int)
, a pointer to a function that takes two int
arguments and returns an int)
An example is declaring a struct type, e.g.:
\nstruct person {
\tchar name[20];
\tint age;
\tfloat height;
};
Can you assign a float variable to an int variable?
\nYes, but the value will be truncated.
Can you assign an int variable to a float variable?
\nYes, but the type will be promoted.
Can you assign any first-class type variable to any other first-class type variable?
\nYes, you just have to typecast them to the matching data type.
Can you assign a first-class type variable to any kind of derived type variable?
\nNo, e.g. you cannot assign an int to a structure
|
#define
is a preprocessor directive in C that unconditionally defines a macro.
#ifdef
is a preprocessor directive in the C programming language that tests whether a macro has been defined or not. It allows conditional compilation of code based on whether a particular macro has been defined or not.
#else
is run if the macro is not defined in a #ifdef
#endif
Ends a #ifdef macro
#if
is a preprocessor directive in the C programming language that allows conditional compilation of code based on the value of an expression.
Does the following program cause a compile-time error?
\n
int function() {
\tint x = 0;
\treturn;
\treturn 0;
}#if
directive, it replaces it with B and then replaces B with C. Therefore, the #if
statement is effectively replaced by #if (C == 1)
.
Since C is defined as 1, the condition in the #if
statement evaluates to true, and the code in the first branch of the if statement is executed, which is a return statement without a value.
In this specific case, the program still works because the function return type is int
, and the return statement in the first branch of the if statement might just return some undetermined number.
In general, however, it is good practice to always explicitly return a value from a function that has a return type, as it makes the code more clear and less error-prone.
What are the reasons for using libraries?
\nTo import useful code, promote modular programming, and provide cross-platform compatibility.
What are the differences between static and dynamic (shared) libraries?
\nAspects | \nStatic library | \nDynamic library | \n
---|---|---|
Linking | \nLinked at compile time | \nLinked at run time | \n
Size | \nIncrease the size of the executable (the library code is included in the executable. | \nReduce the size of the executable (the library code is stored separately and referenced at run time) | \n
Memory Usage | \nIncrease memory usage (the entire library code is loaded into memory) | \nReduce memory usage (the code is shared among multiple processes, and only one copy of the library code is loaded into memory) | \n
Ease of Updates | \nRequire recompilation of the entire program | \nAllow for easier updates (can replace the library file without recompiling the program) | \n
Portability | \nMore portable (does not require the presence of the library file at run time) | \nLess portable (requires the library file to be present and correctly configured at run time) | \n
Runtime Dependencies | \nNo (directly included in the executable) | \nYes (must be present in the correct location for the program to run) | \n
What are the trade-offs between the above two?
\nThe trade-offs between static and dynamic libraries involve executable size, memory usage, ease of updates, runtime dependencies, portability, and performance considerations.
How do you create a library?
\nCompile c files into an object file and link them with
gcc (name).o –shared –o library.so
In the history of mathematics, Pierre de Fermat was a special figure. His formal occupation was as a lawyer, but he was exceptionally fond of mathematics. Although an amateur, Fermat’s achievements in mathematics were no less than those of professional mathematicians of the same era. He contributed to modern calculus, analytic geometry, probability, and number theory. Especially in the field of number theory, Fermat was most interested and achieved the most outstanding results.
\nLogic is the foundation of the certainty of all the knowledge we acquire.
— Leonhard Euler (Swiss mathematician, physicist, astronomer, geographer, logician, and engineer, one of the greatest mathematicians in history)
As the \"king of amateur mathematicians\", Fermat proposed some famous conjectures in number theory but did not give strong proof. The most famous is Fermat's Last Theorem1. Although Fermat claimed he had found an ingenious proof, there was not enough space on the margin to write it down. But in fact, after more than 350 years of unremitting efforts by mathematicians, it was not until 1995 that British mathematician Andrew John Wiles and his student Richard Taylor published a widely recognized proof.
\nIn contrast, there is also a little theorem of Fermat. In October 1640, Fermat first wrote down words equivalent to the following in a letter to a friend:
\n\n\nIf \\(p\\) is a prime and \\(a\\) is any integer not divisible by \\(p\\), then \\(a^{p-1}-1\\) is divisible by \\(p\\).
\n
Similarly, Fermat did not give proof in the letter. Nearly a hundred years later, the complete proof was first published by the great mathematician Euler in 1736. Later, people found in the unpublished manuscripts of another great mathematician Leibniz that he had obtained almost the same proof before 1683.
\nFermat's little theorem is one of the fundamental results of elementary number theory. This theorem can be used to generate primality testing rules and corresponding verification algorithms. In the late 1970s, public key cryptography emerged, and Fermat's little theorem helped prove the correctness of RSA. Afterward, researchers combined it with the Chinese remainder theorem and also discovered an optimized method for RSA decryption and signing. The following further introduces these applications.
\nThe complete statement of Fermat's little theorem is: If \\(\\pmb{p}\\) is a prime number, then for any integer \\(\\pmb{a}\\), the number \\(\\pmb{a^p−a}\\) is an integer multiple of \\(\\pmb{p}\\). In the notation of modular arithmetic, this is expressed as \\(\\pmb{a^p\\equiv a\\pmod p}\\). If \\(\\pmb{a}\\) is not divisible by \\(\\pmb{p}\\), then \\(\\pmb{a^{p-1}\\equiv 1\\pmod p}\\).
\nFrom \\(a^{p-1}\\equiv 1\\pmod p\\) it can be deduced that \\(\\pmb{a^{p-2}\\equiv a^{-1}\\pmod p}\\). This new congruence just gives a way to find the multiplicative inverse of \\(a\\) modulo \\(p\\). This is a direct corollary of Fermat's little theorem.
\nAnother important corollary is: If \\(\\pmb{a}\\) is not a multiple of \\(\\pmb{p}\\) and \\(\\pmb{n=m\\bmod {(p-1)}}\\), then \\(\\pmb{a^n\\equiv a^m\\pmod p}\\). This inference does not seem very intuitive, but the proof is simple:
\nThere are many ways to prove Fermat's little theorem. Among them, mathematical induction based on the binomial theorem is the most intuitive one. First, for \\(a=1\\), it is obvious that \\(1^p \\equiv 1\\pmod{p}\\) holds. Now assume that for an integer \\(a\\), \\(a^p \\equiv a \\pmod{p}\\) is true. As long as it is proved under this condition that \\((a+1)^p\\equiv a+1\\pmod{p}\\), the proposition holds.
\nAccording to the binomial theorem, \\[(a+1)^p = a^p + {p \\choose 1} a^{p-1} + {p \\choose 2} a^{p-2} + \\cdots + {p \\choose p-1} a + 1\\] Here the binomial coefficient is defined as \\({p \\choose k}= \\frac{p!}{k! (p-k)!}\\). Note that because \\(p\\) is a prime number, for \\(1≤k≤p-1\\), each binomial coefficient \\({p \\choose k}\\)is a multiple of \\(p\\).
\nThen taking \\(\\bmod p\\), all the intermediate terms disappear, leaving only \\(a^p+1\\) \\[(a+1)^p \\equiv a^p + 1 \\pmod{p}\\]Referring to the previous assumption \\(a^p ≡ a \\pmod p\\), it infers that \\((a+1)^p \\equiv a+1 \\pmod{p}\\), the proof is complete.
\nFermat's little theorem provides concise solutions to some seemingly complicated computational problems. First look at a simple example: If today is Sunday, what day will it be in \\(2^{100}\\) days? There are 7 days in a week. According to Fermat's little theorem, we have \\(2^{7−1}≡1\\bmod 7\\), from which we can get \\[2^{100}=2^{16×6+4} ≡ 1^{16}×2^4≡16≡2\\pmod 7\\]So the answer is Tuesday. This actually repeats the proof process of the second corollary above with specific numbers. Applying this corollary can greatly speed up modular exponentiation. For example, to calculate \\(49^{901}\\bmod 151\\), since \\(901\\bmod(151-1)=1\\), it can be deduced immediately that \\[49^{901}\\equiv 49^1\\equiv 49\\pmod {151}\\]
\nNow look at a question that seems a little more difficult: Given the equation \\(133^5+110^5+84^5+27^5=n^{5}\\), find the value of \\(n\\).
\nAt first glance, there seems to be no clue, so start with basic parity checking. The left side of the equation has two odd terms and two even terms, so the total is even, which also determines that \\(n\\) must be even. Looking at the exponent 5 which is a prime number, and thinking of Fermat's little theorem, we get \\(n^5≡n\\pmod 5\\), therefore \\[133^5+110^5+84^5+27^5≡n\\pmod 5\\] \\[3+0+4+2≡4≡n\\pmod 5\\] Continuing to take modulo 3, according to the corollary of Fermat's little theorem again, we have \\(n^5≡n^{5\\mod(3-1)}≡n\\pmod 3\\). So \\[133^5+110^5+84^5+27^5≡n\\pmod 3\\] \\[1+2+0+0≡0≡n\\pmod 3\\]
\nOkay, now summarize:
\nThese lead to \\(n = 144\\) or \\(n\\geq 174\\). Obviously, 174 is too big. It can be concluded that n can only be 144.
\nThis question actually appeared in the 1989 American Invitational Mathematics Examination (AIME), which is a math competition for high school students. Interestingly, the solution to the question happens to disprove Euler's conjecture.
\nMany encryption algorithm applications require \"random\" large prime numbers. The common method to generate large primes is to randomly generate an integer and then test for primality. Since Fermat’s little theorem holds on the premise that p is a prime number, this provides a prime test method called the Fermat primality test. The test algorithm is
\n\n\nInput: \\(n\\) - the number to be tested, \\(n>3\\); \\(k\\) - the number of iterations
\n
\nOutput: \\(n\\) is composite, otherwise may be prime
\nRepeat k times:
\n\\(\\quad\\quad\\)Randomly select an integer \\(a\\) between \\([2, n-2]\\)
\n\\(\\quad\\quad\\)If \\(a^{n-1}\\not \\equiv 1{\\pmod n}\\), return \\(n\\) is composite
\nReturn \\(n\\) may be prime
It can be seen that Fermat’s primality test is non-deterministic. It uses a probabilistic algorithm to determine whether a number is composite or probably prime. When the output is composite, the result is definitely correct; but those numbers tested to be probably prime may actually be composite, such numbers are called Fermat pseudoprimes. The smallest Fermat pseudoprime is 341, with \\(2^{340}\\equiv1\\pmod {341}\\) but \\(341=11×31\\). So in fact, Fermat's little theorem provides a necessary but insufficient condition for determining prime numbers. It can only be said that the more iterations performed, the higher the probability that the tested number is prime.
\nThere is also a class of Fermat pseudoprimes \\(n\\) which are composite numbers themselves, but for any integer \\(x\\) that is coprime with \\(n\\), it holds \\(x^{n-1}\\equiv 1\\pmod n\\). In number theory, they are called Carmichael numbers. The smallest Carmichael number is 561, equal to \\(3×11×17\\). Carmichael numbers can fool Fermat’s primality test, making the test unreliable. Fortunately, such numbers are very rare. Statistics show that among the first \\(10^{12}\\) natural numbers there are only 8241 Carmichael numbers.
\nThe PGP encryption communication program uses Fermat’s primality test in its algorithm. In network communication applications requiring large primes, Fermat’s primality test method is often used for pretesting, followed by calling the more efficient Miller-Rabin primality test to ensure high accuracy.
\nFermat's little theorem can also be used to prove the correctness of the RSA algorithm, that is, the decryption formula can completely restore the plaintext \\(m\\) from the ciphertext \\(c\\) without errors: \\[c^d=(m^{e})^{d}\\equiv m\\pmod {pq}\\]
\nHere \\(p\\) and \\(q\\) are different prime numbers, \\(e\\) and \\(d\\) are positive integers that satisfy \\(ed≡1\\pmod {λ(pq)}\\), where \\(λ(pq)=\\mathrm{lcm}(p−1,q−1)\\). \\(\\mathrm{lcm}\\) is the least common multiple function.
\nBefore starting the proof, first introduce a corollary of the Chinese remainder theorem: If integers \\(\\pmb{n_1,n_2,...,n_k}\\) are pairwise coprime and \\(\\pmb{n=n_{1}n_{2}...n_{k}}\\), then for any integer \\(\\pmb x\\) and \\(\\pmb y\\), \\(\\pmb{x≡y\\pmod n}\\) holds if and only if \\(\\pmb{x≡y\\pmod{n_i}}\\) for each \\(\\pmb{i=1,2,...k}\\). This corollary is easy to prove, details are left as an exercise2. According to this corollary, if \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) are both true, then \\(m^{ed}≡m\\pmod{pq}\\) must also hold.
\nNow look at the first step of the proof. From the relationship between \\(e\\) and \\(d\\), it follows \\(ed-1\\) can be divided by both \\(p-1\\) and \\(q-1\\), that is, there exist non-negative integers \\(h\\) and \\(k\\) satisfying: \\[ed-1=h(p-1)=k(q-1)\\]
\nThe second step is to prove \\(m^{ed}≡m\\pmod p\\). Consider two cases:
\nThe third step has the goal of proving \\(m^{ed}≡m\\pmod q\\). The deduction process is similar to the previous step, and it can also be deduced that m^ed ≡ m (mod q):
\nSince both \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) have been proved, \\(m^{ed}≡m\\pmod{pq}\\) holds, Q.E.D.
\nCombining Fermat’s little theorem and the Chinese remainder theorem can not only verify the correctness of the RSA but also deduce an optimized decryption method.
\nIn the RSA encryption algorithm, the modulus \\(N\\) is the product of two prime numbers \\(p\\) and \\(q\\). Therefore, for any number \\(m\\) less than \\(N\\), letting \\(m_1=m\\bmod p\\) and \\(m_2=m\\bmod q\\), \\(m\\) is uniquely determined by \\((m_1,m_2)\\). According to the Chinese remainder theorem, we can use the general solution formula to deduce \\(m\\) from \\((m_1,m_2)\\). Since \\(p\\) and \\(q\\) each have only half the number of bits as \\(N\\), modular arithmetic will be more efficient than directly computing \\(c^d\\equiv m\\pmod N\\). And in the process of calculating \\((m_1,m_2)\\), applying the corollary of Fermat's little theorem yields: \\[\\begin{align}\nm_1&=m\\bmod p=(c^d\\bmod N)\\bmod p\\\\\n&=c^d\\bmod p=c^{d\\mod(p-1)}\\bmod p\\tag{1}\\label{eq1}\\\\\nm_2&=m\\bmod q=(c^d\\bmod N)\\bmod q\\\\\n&=c^d\\bmod q=c^{d\\mod(q-1)}\\bmod q\\tag{2}\\label{eq2}\\\\\n\\end{align}\\]
\nObviously, in above \\((1)\\) and \\((2)\\) the exponent \\(d\\) is reduced to \\(d_P=d\\bmod (p-1)\\) and \\(d_Q=d\\bmod (q-1)\\) respectively, which further speeds up the calculation. Finally, the step of calculating \\(m\\) is further optimized using the Garner algorithm3: \\[\\begin{align}\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\\\\\nh&=q_{\\text{inv}}(m_{1}-m_{2})\\pmod {p}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{3}\\label{eq3}\n\\end{align}\\] Note that given \\((p,q,d)\\), the values of \\((d_P,d_Q,q_\\text{inv})\\) are determined. So they can be precomputed and stored. For decryption, only \\((m_1,m_2,h)\\) are to be calculated and substituted into the above (3).
\nThis is actually the decryption algorithm specified in the RSA cryptography standard RFC 8017 (PKCS #1 v2.2). The ASN.1 formatted key data sequence described by this specification corresponds exactly to the above description (\\(d_P\\) - exponent1,\\(d_Q\\) - exponent2,\\(q_{\\text{inv}}\\) - coefficient):
\nRSAPrivateKey ::= SEQUENCE { |
The widely used open-source library OpenSSL implements this efficient and practical decryption algorithm. As shown below, the key data generated using the OpenSSL command line tool is consistent with the PKCS #1 standard:
\n# Generate 512-bit RSA keys saved in PEM format file. |
Also known as \"Fermat's conjecture\",its gist is that, when \\(n > 2\\), the equation \\(x^{n}+y^{n}=z^{n}\\) has no positive integer solutions \\((x, y, z)\\). After it was finally proven correct in 1995, it became known as \"Fermat's last theorem.\"↩︎
Hint: If two integers are congruent modulo \\(n\\), then \\(n\\) is a divisor of their difference.↩︎
Garner, H., \"The Residue Number System\", IRE Transactions on Electronic Computers, Volume EC-8, Issue 2, pp.140-147, DOI 10.1109/TEC.1959.5219515, June 1959↩︎
About the IP packet header checksum algorithm, simply put, it is 16-bit ones' complement of the ones' complement sum of all 16-bit words in the header. However, not many sources show exactly how this is done. The same checksum algorithm is used by TCP segment and UDP datagram, but the data involved in the checksum computing is different from that in the IP header. In addition, the checksum operation of the IPv6 packet is different from that of IPv4. Therefore, it is necessary to make a comprehensive analysis of the checksum algorithm of IP packets.
\nNothing in life is to be feared, it is only to be understood.
— Marie Curie (Polish and naturalized-French physicist and chemist, twice Nobel Prize winner)
IPv4 packet header format can be seen below
\n0 1 2 3 |
Here the 16-bit Header Checksum field is used for error-checking of the IPv4 header. While computing the IPv4 header checksum, the sender first clears the checksum field to zero, then calculates the sum of each 16-bit value within the header. The sum is saved in a 32-bit value. If the total number of bytes is odd, the last byte is added separately.
\nAfter all additions, the higher 16 bits saving the carry is added to the lower 16 bits. Repeat this till all higher 16 bits are zeros. Finally, the sender takes the ones' complement of the lower 16 bits of the result and writes it to the IP header checksum field.
\nThe following demonstrates the entire calculation process using actual captured IPv4 packets.
\n0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00 |
At the beginning of the above 16-bit hex dump is the Ethernet frame header. The IP packet header starts from offset 0x000e, with the first byte 0x45 and the last byte 0xe9. Based on the previous description of the algorithm, we can make the following calculations:
\n(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 + |
Notice at step (1) we replace the checksum field with 0x0000. As can be seen, the calculated header checksum 0x598f is the same as the value in the captured packet. This calculating process is only used for the sender to generate the initial checksum. In practice, for the intermediate forwarding router and the final receiver, they can just sum up all header fields of the received IP packet by the same algorithm. If the result is 0xffff, the checksum verification passes.
\nHow to program IPv4 header checksum computing? RFC 1071 (Computing the Internet Checksum) shows a reference \"C\" language implementation:
\n{ |
In a real network connection, the source device can call the above code to generate the initial IPv4 header checksum. This checksum is then updated at each step of the routing hop because the router must decrement the Time To Live (TTL) field. RFC 1141 (Incremental Updating of the Internet Checksum) gives a reference implementation of fast checksum update:
\nunsigned long sum; |
For TCP segment and UDP datagram, both have 16-bit header checksum fields used for error-checking by the destination host. The checksum computing algorithm is the same as the IP header, except for the difference of covered data. Here the checksum is calculated over the whole TCP/UDP header and the payload, plus a pseudo-header that mimics the IPv4 header as shown below:
\n0 7 8 15 16 23 24 31 |
It consists of the source and destination IP addresses, the protocol number (TCP:6/UDP:17), and the total length of the TCP/UDP header and payload (in bytes). The purpose of including the pseudo-header in the checksum computing is to confirm the packet reaches the expected destination and avoid IP spoofing attacks. Besides, for IPv4 UDP header checksum is optional, it carries all-zeros if unused.
\nIPv6 is IP protocol version 6, and its main design goal was to resolve the problem of IPv4 address exhaustion. Of course, it provides many benefits in other aspects. Although IPv6 usage is growing slowly, the trend is unstoppable. The latest IPv6 standard is published in RFC 8200(Internet Protocol, Version 6 (IPv6) Specification).
\nIPv6 packet header format can be seen below
\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
Notice that the IPv6 header does not include a checksum field, a significant difference from IPv4. The absence of a checksum in the IPv6 header furthers the end-to-end principle of Internet design, to simplify router processing and speed up the packet transmission. Protection for data integrity can be accomplished by error detection at the link layer or the higher-layer protocols between endpoints (such as TCP/UDP on the transport layer). This is why IPv6 forces the UDP layer to set the header checksum.
\nFor IPv6 TCP segment and UDP datagram header checksum computing, the pseudo-header that mimics the IPv6 header is shown below
\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
In actual IPv6 network applications, UDP-Lite (Lightweight UDP) can be used to balance error detection and transmission efficiency. UDP-Lite has its own protocol number 136, and its standard is described in RFC 3828 (The Lightweight User Datagram Protocol (UDP-Lite)).
\nReferring to the following header format, UDP-Lite uses the same set of port number values assigned by the IANA for use by UDP. But it redefines the Length field in the UDP header to a Checksum Coverage, which allows the application layer to control the length of checksummed data. This is useful for the application that can be tolerant of the potentially lossy transmission of the uncovered portion of the data.
\n0 15 16 31 |
UDP-Lite protocol defines the values of \"Checksum Coverage\" (in bytes) as shown in the following table:
\nChecksum Coverage | \nCoverage Area | \nDescription | \n
---|---|---|
0 | \nentire UDP-Lites datagram | \nCalculation covers IP pseudo-header | \n
1-7 | \n(invalid) | \nThe receiver has to drop the datagram | \n
8 | \nUDP-Lites header | \nCalculation covers IP pseudo-header | \n
> 8 | \nUDP-Lites header + portion of payload data | \nCalculation covers IP pseudo-header | \n
> IP datagram length | \n(invalid) | \nThe receiver has to drop the datagram | \n
For multimedia applications running VoIP or streaming video data transmission protocols, it'd better receive data with some degree of corruption than not receiving any data at all. Another example is the CAPWAP protocol used to connect Cisco wireless controller and access points. It specifies UDP-Lite as the default transport protocol for the CAPWAP Data channel, while the connection is established over the IPv6 network.
\nAt last, share a C program snippet to present how to initialize a Berkeley socket to establish an IPv6 UDP-Lite connection:
\n
|
Here IPPROTO_UDPLITE
is protocol number 136, which is used together with AF_INET6
address family parameter in socket()
function call for IPv6 socket creation. The UDPLITE_SEND_CSCOV
(10) and UDPLITE_RECV_CSCOV
(11) are the control parameters of socket options configuration function setsockopt()
, used for setting the Checksum Coverage value in the sender and the receiver respectively. Remember that both the sender and the receiver must set the same value, otherwise, the receiver will not be able to verify the checksum properly.
Recently, at a WPA3 technology introduction meeting within the R&D team, the speaker mentioned that the OWE technology for encrypted wireless open networks is based on Diffie-Hellman key exchange, and casually said that Diffie-Hellman key exchange is using technology similar to RSA. This statement is wrong! Although Diffie-Hellman key exchange and RSA encryption algorithms belong to public key cryptography, their working mechanisms and application scenarios are different. As a research and development engineer and technician supporting network security, it is necessary to clearly understand the working mechanism and mathematical principles of the two, as well as the differences and connections between them.
\nA cryptographic system should be secure even if everything about the system, except the key, is public knowledge.
— Auguste Kerckhoffs (Dutch linguist and cryptographer, best known for his “Kerckhoffs's principle” of cryptography)
Diffie-Hellman key exchange (DH for short) is a secure communication protocol that allows two communicating parties to exchange messages over an insecure public channel to create a shared secret without any foreknowledge. This secret can be used to generate keys for subsequent communications between the two parties using symmetric encryption techniques (e.g. AES).
\nThe idea of this kind of public key distribution to achieve shared secrets was first proposed by Ralph Merkle, a doctoral student of Stanford University professor Martin Hellman, and then Professor Hellman's research assistant Whitfield Diffie and Professor Herman jointly invented a practical key exchange protocol. In 1976, Diffie and Hellman were invited to publish their paper \"New Directions in Cryptography\" in IEEE Transactions on Information Theory, which laid the foundation for the public key cryptography system and officially announced the birth of the new Diffie-Herman key exchange technology.
\nThe working principle of Diffie-Hellman key exchange is based on the modular exponentiation operation with the multiplicative group of integers modulo n and its primitive root modulo n in number theory. The following is a simple and specific example to describe:
\nIs it troublesome calculating \\(\\color{#93F}{\\bf62^{39}\\bmod\\;71}\\)? It is actually very easy……
\nRemember that modular arithmetic has the property of preserving primitive operations: \\[(a⋅b)\\bmod\\;m = [(a\\bmod\\;m)⋅(b\\bmod\\;m)]\\bmod\\;m\\] Combining with the principle of Exponentiation by Squaring, and applying the right-to-left binary method to do fast calculation: \\[\\begin{align}\n62^{39}\\bmod\\;71 & = (62^{2^0}⋅62^{2^1}⋅62^{2^2}⋅62^{2^5})\\bmod\\;71\\\\\n& = (62⋅10⋅(62^{2^1}⋅62^{2^1})⋅(62^{2^4}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅(10⋅10)⋅(62^{2^3}⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(29⋅29⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(60⋅60⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(50⋅50))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅15)\\bmod\\;71\\\\\n& = 42\n\\end{align}\\]
\n\nAs if by magic, both Alice and Bob get the same \\(s\\) value of \\(42\\). This is the shared secret of two people! After this, Alice and Bob can use the hash value of \\(s\\) as a symmetric key for encrypted communication, which is unknown to any third party.
\nWhy? Because of the nature of the modular exponentiation of the multiplicative group, \\(g^{ab}\\) and \\(g^{ba}\\) are equal with the modulo \\(p\\):
\n\\[A^b\\bmod\\;p=g^{ab}\\bmod\\;p=g^{ba}\\bmod\\;p=B^a\\bmod\\;p\\]
\nSo calculated \\(s\\) values must be the same. Of course, real applications would use much larger \\(p\\), otherwise the attacker can exhaust all the remainder to try to crack the ciphertext encrypted by the symmetric key.
\nNotice \\((p,g,A,B)\\) is public and \\((a,b,s)\\) is secret. Now suppose an eavesdropper Eve can see all the messages between Alice and Bob, can she deduce \\(s\\)? The answer is that this is only practically possible if the values of \\((p,a,b)\\) are very small. Eve must first invert \\((a,b)\\) from what she knows about \\((p,g,A,B)\\):
\nThis is the famous discrete logarithm problem. It is a recognized computational challenge and no polynomial-time efficient algorithm is currently found to compute the discrete logarithm. So this protocol is considered eavesdropping-safe as long as the appropriate \\((p,a,b)\\) is chosen. RFC 3526 recommends 6 Modular Exponential (MODP) DH groups of large prime numbers for practical applications, the smallest of which has 1536 bits!
\nIt should also be emphasized that Diffie-Hellman key exchange itself does not require authentication of both communicating parties, so it is vulnerable to man-in-the-middle attacks. If an attacker can tamper with the messages sent and received by both sides in the middle of the channel, he can complete Diffie-Hellman key exchange twice by pretending to be an identity. The attacker can then decrypt the entire message. Therefore, usually practical applications need to incorporate authentication mechanisms to prevent such attacks.
\nDiffie-Hellman key exchange technique is a crucial contribution to modern cryptography. In 2015, 39 years after the announcement of this invention, Diffie and Hellman jointly won the ACM Turing Award, known as the \"Nobel Prize of Computing\". The ACM award poster directly stated that they \"invented public key cryptography\".
\nRSA is a public key encryption algorithm. The public key encryption system with the same name as the core technology is widely used in secure data transmission. Today, the comprehensive development of the Internet has provided great convenience to the public in all aspects of society. Whether you are surfing, gaming, entertaining, shopping, instant messaging with friends and family, managing a bank account, investing in financial securities, or simply sending and receiving email, RSA is working behind the scenes to protect your privacy and data security.
\nRSA is actually an acronym for the last names of three people: American cryptographer Ronald Rivest, Israeli cryptographer Adi Shamir, and American computer scientist Leonard Max Adleman. In 1977, Levister, Shamir, and Adleman collaborated at the Massachusetts Institute of Technology (MIT) to invent the RSA encryption algorithm. The algorithm was first published in a public technical report at MIT, and later compiled and published in the February 1978 issue of ACM Communications under the title \"A Method for Obtaining Digital Signatures and Public Key Cryptosystems\".
\nThe basic idea of RSA is that the user creates a key pair consisting of a public key and a private key. The public key is freely distributed and the private key must be kept secret. Anyone can encrypt a message with the public key, and the resulting ciphertext can only be deciphered by the private key holder. On the other hand, any message encrypted with the private key can be decrypted by the public key. Since we assume that the private key can only be held by a specific object, encrypting with the private key is equivalent to generating a digital signature, and decrypting with the public key is equivalent to verifying the signature.
\nThe RSA encryption algorithm consists of a four-step operational process: key generation, key distribution, encryption, and decryption. A simple and concrete example is also given below to illustrate.
\nThe third step above works out \\(d\\) from \\(\\color{#93F}{\\bf(d\\cdot 5)\\;mod\\;52794=1}\\), here's how
\nThe modular multiplicative invers can be solved quickly by applying the Extended Euclidean algorithm. Referring to this Wiki page, with the precondition of coprime, the following equation can be written (\\(gcd\\) is the function for the greatest common divisor function):
\n\\[52794s+5t=\\mathrm{gcd}(5, 52794)=1\\]
\nThe goal is to find the smallest positive integer \\(t\\) that satisfies the above equation. The following table shows the iterative process of the algorithm:
\nIndex \\(i\\) | \nQuotient \\(q_{i-1}\\) | \nRemainder \\(r_i\\) | \n\\(s_i\\) | \n\\(t_i\\) | \n
---|---|---|---|---|
0 | \n\n | \\(52794\\) | \n\\(1\\) | \n\\(0\\) | \n
1 | \n\n | \\(5\\) | \n\\(0\\) | \n\\(1\\) | \n
2 | \n\\(52794 \\div5 = 10558\\) | \n\\(4\\) | \n\\(1 - 10558\\times 0 = 1\\) | \n\\(0 - 10558\\times 1 = -10558\\) | \n
3 | \n\\(5 \\div4 = 1\\) | \n\\(1\\) | \n\\(0-1\\times1 = -1\\) | \n\\(1 - 1\\times (-10558) = \\bf10559\\) | \n
It only takes two iterations to get the remainder \\(1\\) and the algorithm ends. The final \\(t\\) is the \\(5^{-1}\\pmod {52794}\\) we want.
\n\nString together after decoding to get the same information \"CACC 9678\". Why does Alice's decrypted message match exactly the one sent by Bob? The reason lies in the modular exponentiation operation. First of all, because \\(c\\equiv m^e\\pmod N\\), we can get \\(c^d\\equiv (m^e)^d \\equiv m^{ed} \\pmod N\\). Since \\((d⋅e)\\;mod\\;\\lambda(N)=1\\), it is deduced that \\(ed = 1 + h\\lambda(N)\\) (\\(h\\) is a non-negative integer为非负整数). Combine these two
\n\\[\\Rightarrow m^{ed} = m^{(1+h\\lambda(N))} = \\color{fuchsia}{m(m^{\\lambda(N)})^h \\equiv m(1)^h}\\equiv m\\pmod N\\]
\nThe penultimate congruence above (symbol \\(\\equiv\\)) is based on Euler's theorem). This proves the correctness of the decryption formula \\({m\\equiv c^d\\pmod N}\\)! You can also see that the order of \\(e\\) and \\(d\\) is irrelevant for the result of \\(m^{ed}\\pmod N\\), so the message that Alice encrypted with her private key can be decrypted by Bob with Alice's public key. This also proves the feasibility of digital signatures.
\nIn terms of security, if a third party can derive \\(d\\) from Alice's public key \\((N,e)\\), then the algorithm is broken. But the prerequisite for cracking is to first identify \\(p\\) and \\(q\\) from \\(N\\), which is very difficult when \\(N\\) is big. In fact, this is the famous problem of factoring large numbers, another recognized computational challenge. So far, \"the best-known algorithms are faster than exponential order of magnitude times and slower than polynomial order of magnitude times.\" The latest record, published on the RSA Factoring Challenge website, is the February 2020 crack of RSA-250, a large number of 829 bits. This development indicates that the security of 1024-bit \\(N\\)-valued public keys is already in jeopardy. In view of this, National Institute of Standards and Technology (NIST) recommends that RSA keys be at least 2048 bits in length for real-world applications.
\nOn the other hand, although the public key does not need to be transmitted confidentially, it is required to be reliably distributed. Otherwise, Eve could pretend to be Alice and send her own public key to Bob. If Bob believes it, Eve can intercept all messages passed from Bob to Alice and decrypt them with her own private key. Eve will then encrypt this message with Alice's public key and pass it to her. Alice and Bob cannot detect such a man-in-the-middle attack. The solution to this problem is to establish a trusted third-party authority to issue certificates to ensure the reliability of public keys. This is the origin of the Public Key Infrastructure (PKI).
\nThe RSA public key encryption algorithm is the genius creation of three cryptographers and computer scientists. Its invention is a new milestone in public key cryptography and has become the cornerstone of modern secure Internet communication. The outstanding contribution of Levister, Shamir, and Adelman earned them the ACM Turing Award in 2002, a full 13 years before Diffie and Herman!
\nThe following table summarizes the comparison of Diffie-Hellman key exchange and RSA public key encryption algorithm:
\nCryptographic Technology | \nDiffie-Hellman Key Exchange | \nRSA Encryption Algorithm | \n
---|---|---|
Technology Category | \nAsymmetric, Public Key Technology | \nAsymmetric, Public Key Technology | \n
Mathematical Principles | \nInteger modulo \\(n\\) multiplicative groups, primitive roots | \nCarmichael function, modular multiplicative inverse, Euler's theorem | \n
Mathematical Operations | \nModular exponentiation, exponentiation by squaring | \nModular exponentiation, exponentiation by squaring, extended Euclidean algorithms | \n
Public Key | \n\\((p,g,A,B)\\) | \n\\((N,e)\\) | \n
Private Key | \n\\((a,b,s)\\) | \n\\((N,d)\\) | \n
Security | \nDiscrete logarithm problem | \nLarge number prime factorization problem | \n
Typical Applications | \nKey Exchange | \nEncryption/Decryption, Digital Signature | \n
Key Kength | \n\\(\\ge2048\\) bits | \n\\(\\ge2048\\) bits | \n
Authentication | \nRequires external support | \nRequires PKI support for public key distribution | \n
Forward Secrecy | \nSupport | \nNot support | \n
As can be seen, both are asymmetric public key techniques, and both have a public and private key pair. They both use Modular exponentiation and exponentiation by squaring mathematical operations, and the RSA public-key encryption algorithm also requires the application of the extended Euclidean algorithm to solve the modular multiplicative inverse. Despite these similarities, the mathematical principles underlying them are different, and the computational challenges corresponding to their security are different in nature. These characteristics determine that the Diffie-Hellman key exchange can be used for key exchange, but not for encryption/decryption, while the RSA public key encryption algorithm can not only encrypt/decrypt but also support digital signatures. Therefore, the argument that the two use similar technologies cannot be established in general.
\nElGamal encryption based on the evolution of the Diffie-Hellman key exchange can be used to encrypt/decrypt messages, but due to some historical reasons and the great commercial success of the RSA public key encryption algorithm, ElGamal encryption is not popular.
\nIn modern cryptography, key length is defined as the number of bits of a key used by an encryption algorithm. Theoretically, since all algorithms may be cracked by brute force, the key length determines an upper limit on the security of an encryption algorithm. Cryptanalytic study shows that the key strengths of Diffie-Hellman key exchange and RSA public key encryption algorithm are about the same. The computational intensities for breaking discrete logarithms and factoring large numbers are comparable. Therefore, the recommended key length for both cryptographic technologies in practical applications is at least 2048 bits.
\nFor authentication, Diffie-Hellman key exchange requires external support, otherwise it is not resistant to man-in-the-middle attacks. RSA public key encryption algorithm can be used to verify digital signatures, but only if there is a PKI supporting reliable public key distribution. The current system of PKI is quite mature, and there is a special Certificate Authority (CA) that undertakes the responsibility of public key legitimacy checking in the public key system, as well as issues and manages public key digital certificates in X.509 format.
\nOne problem with the RSA public key encryption algorithm in practice is that it does not have Forward Secrecy. Forward Secrecy, sometimes referred to as Perfect Forward Secrecy, is a security property of confidential communication protocols, meaning that the leakage of the long-term used master key does not result in the leakage of past session information. If the system has forward secrecy, it can protect the historical communication records in case of private key leakage. Imagine a situation where, although Eve cannot decrypt the RSA-encrypted messages between Alice and Bob, Eve can archive the entire past message ciphertext. One day in the future, Alice's private key for some reason was leaked, then Eve can decrypt all the message records.
\nThe solution to this problem is Diffie-Hellman key exchange! Remember that the \\((A,B)\\) in the public key of the Diffie-Hellman key exchange is generated by both parties from their respective private keys \\((a,b)\\), so if a random \\((a,b)\\) value is generated at each session, future key leaks will not crack the previous session key. This shows that Diffie-Hellman key exchange supports forward secrecy! If we combine the forward secrecy of Diffie-Hellman key exchange with the digital signature feature of the RSA public key encryption algorithm, we can implement a key exchange with authentication protection. This process can be simplified by the following example.
\nHere the RSA digital signature safeguards the key exchange from man-in-the-middle attacks. Also in the second step above, if a new random number is generated for each session, then even if Alice's or Bob's RSA private keys are leaked one day, it does not threaten the security of previous sessions because the eavesdropper still has to solve the discrete logarithm puzzle. We have also achieved forward secrecy. In fact, this is the working mechanism of the DHE-RSA cipher suite as defined by the ubiquitous Transport Layer Security (TLS) protocol.
\nTransport Layer Security (TLS) and its predecessor Secure Sockets Layer (SSL) is a security protocol that provides security and data integrity for Internet communications. TLS is widely used in applications such as browsers, email, instant messaging, VoIP, and virtual private networks (VPNs), and has become the de facto industry standard for secure Internet communications. Currently, TLS 1.2 is the commonly supported version of the protocol, supporting secure connections over TCP. Datagram Transport Layer Security (DTLS) protocol is also defined for UDP applications. DTLS is much the same as TLS, with some extensions for connectionless UDP transport in terms of reliability and security. DTLS 1.2 matches the functionality of TLS 1.2.
\nThe TLS protocol uses a client-server architectural model. It works by using X.509 authentication and asymmetric encryption algorithms to authenticate the communicating parties, after which keys are exchanged to generate a symmetric encryption session key. This session key is then used to encrypt the data exchanged between the two communicating parties, ensuring the confidentiality and reliability of the information without fear of attack or eavesdropping by third parties. For identification purposes, the TLS 1.2 protocol combines the authentication, key exchange, bulk encryption, and message authentication code algorithms used into the Cipher Suite name. Each Cipher Suite is given a double-byte encoding. The TLS Cipher Suite Registry provides a reference table of all registered Cipher Suite names, sorted by encoding value from small to large.
\nSince the computation intensity of asymmetric encryption algorithms (RSA, etc.) is much higher than that of symmetric encryption algorithms (AES, etc.), practical applications almost always use symmetric encryption algorithms to encrypt messages in batches in terms of performance.
\nTLS 1.2 protocol supports a series of cipher suites that combine the Diffie-Hellman key exchange with the RSA public key encryption algorithm. They all start with TLS_DH_RSA or TLS_DHE_RSA`. The \"E\" in DHE stands for \"Ephemeral\", which means that a random \\((a,b)\\) value is required to be generated for each session. So TLS_DHE_RSA cipher suite can provide forward secrecy, while TLS_DH_RSA cannot, and the former should be preferred in practical applications.
\nHere we take a typical TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite as an example to explain the process of Diffie-Hellman working with RSA to establish a DTLS session. First, explain the composition of the cipher suite.
\nReferring to the packet file dtls-dhe-rsa.pcap captured from the network port, the following handshake protocol message sequence chart can be obtained
\n\nsequenceDiagram\n\nautonumber\nparticipant C as Client\nparticipant S as Server\nNote over C,S: Handshake Protocol\nrect rgb(230, 250, 255)\nC->>S: Client Hello (Cr, Cipher Suites))\nS-->>C: Hello Verify Request (Cookie)\nC->>S: Client Hello (Cr, Cookie, Cipher Suites)\nS-->>C: Server Hello (Sr, Cipher Suite), Certificate (Sn, Se)\nS-->>C: Server Key Exchange (p,g,A,Ss)\nS-->>C: Certificate Request, Server Hello Done\nC->>S: Certificate (Cn, Ce)\nC->>S: Client Key Exchange (B)\nC->>S: Certificate Verify (Cs)\nend\nNote over C,S: Establish Secure Channel\nrect rgb(239, 252, 202)\nC->>S: Change Cipher Spec, Encrypted Handshake Message\nS-->>C: Change Cipher Spec, Encrypted Handshake Message\nC->>S: Application Data\nS-->>C: Application Data\nend\n \n\n
Below is the analysis with regard to the data package numbers in the message sequence chart:
\nHello verification is specific to DTLS to prevent denial of service attacks. The protocol stipulates that the server will not continue to serve the client until it receives a hello message containing the copied cookie.
\nNote: If DH-RSA cipher suite is used, the server-side DH public key parameters \\((p,g,A)\\) are unchanged and will be included directly in its certificate message. At this time, the server will not issue a Key Exchange message \\(\\require{enclose}\\enclose{circle}{5}\\). For DHE-RSA, the \\(A\\) value is different for each session.
\nThis is the complete process of establishing a secure message channel using the TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite, where DHE implements a key exchange with forward secrecy protection and RSA digital signature provides authentication for DHE, creating a solution for secure communication. With a clear understanding of this, we will better grasp the working mechanism of Diffie-Hellman and RSA, effectively apply them in practice and avoid unnecessary mistakes.
\n","categories":["Study Notes"],"tags":["Cryptography","Network Security"]},{"title":"Understand Endianness","url":"/en/2021/12/24/Endianness/","content":"The problem of Endianness is essentially a question about how computers store large numbers.
\nI do not fear computers. I fear lack of them.
— Isaac Asimov (American writer and professor of biochemistry, best known for his hard science fiction)
We know that one basic memory unit can hold one byte, and each memory unit has its address. For an integer larger than decimal 255 (0xff in hexadecimal), more than one memory unit is required. For example, 4660 is 0x1234 in hexadecimal and requires two bytes. Different computer systems use different methods to store these two bytes. In our common PC, the least-significant byte 0x34 is stored in the low address memory unit and the most-significant byte 0x12 is stored in the high address memory unit. While in Sun workstations, the opposite is true, with 0x34 in the high address memory unit and 0x12 in the low address memory unit. The former is called Little Endian
and the latter is Big Endian
.
How can I remember these two data storing modes? It is quite simple. First, remember that the addresses of the memory units we are talking about are always arranged from low to high. For a multi-byte number, if the first byte in the low address you see is the least-significant byte, the system is Little Endian
, where Little matches low
. On the contrary is Big Endian
, where Big corresponds to \"high\".
To deepen our understanding of Endianness, let's look at the following example of a C program:
\nchar a = 1; \t \t \t |
On Intel 80x86 based systems, the memory content corresponding to variables a, b, c, and d are shown in the following table:
\nAddress Offset | \nMemory Content | \n
---|---|
0x0000 | \n01 02 FF 00 | \n
0x0004 | \n11 22 33 44 | \n
We can immediately tell that this system is Little Endian
. For a 16-bit integer short c
, we see the least-significant byte 0xff first, and the next one is 0x00. Similarly for a 32-bit integer long d
, the least-significant byte 0x11 is stored at the lowest address 0x0004. If this is in a Big Endian
computer, memory content would be 01 02 00 FF 44 33 22 11.
At the run time all computer processors must choose between these two Endians. The following is a shortlist of processor types with supported Endian modes:
\nBig Endian
: Sun SPARC, Motorola 68000, Java Virtual MachineBig Endian
mode: MIPS with IRIX, PA-RISC, most Power and PowerPC systemsLittle Endian
mode: ARM, MIPS with Ultrix, most DEC Alpha, IA-64 with LinuxLittle Endian
: Intel x86, AMD64, DEC VAXHow to detect the Endianess of local system in the program? The following function can be called for a quick check. If the return value is 1, it is Little Endian
, else Big Endian
:
int test_endian() { |
Endianness is also important for computer communications. Imagine that when a Little Endian
system communicates with a Big Endian
system, the receiver and sender will interpret the data completely differently if not handled properly. For example, for the variable d in the C program segment above, the Little Endian
sender sends 11 22 33 44 four bytes, which the Big Endian
receiver converts to the value 0x11223344. This is very different from the original value. To solve this problem, the TCP/IP protocol specifies a special \"network byte order\" (referred to as \"network order\"), which means that regardless of the Endian supported by the computer system, the most-significant byte is always sent first while transmitting data. From the definition, we can see that the network order corresponds to the Big Endian
.
To avoid communication problems caused by Endianness and to facilitate software developers to write portable programs, some C preprocessing macros are defined for conversion between network bytes and local byte order. htons()
and htonl()
are used to convert local byte order to network byte order, the former works with 16-bit unsigned numbers and the latter for 32-bit unsigned numbers. ntohs()
and ntohl()
implement the conversion in the opposite direction. The prototype definitions of these four macros can be found as follows (available in the netinet/in.h
file on Linux systems).
IPv6 supports multiple addresses, making address assignments more flexible and convenient. Unlike IPv4, which relied solely on the DHCP protocol for address assignment, IPv6 incorporates a native Stateless Address AutoConfiguration SLAAC) protocol. SLAAC can either work alone to provide IPv6 addresses to hosts, or it can work with DHCPv6 to generate new assignment schemes. Here is a comprehensive analysis of the dynamic address allocation mechanism for IPv6.
\nWho the hell knew how much address space we needed?
— Vint Cerf (American Internet pioneer and one of \"the fathers of the Internet\")
The most significant difference between IPv6 and IPv4 is its large address space. IPv4 has 32 bits (4 bytes) and allows for approximately 4.29 (232) billion addresses. IPv6, on the other hand, defines 128 bits (16 bytes) and supports approximately 340 x 1036 addresses. This is a pretty impressive number, and there will be no address depletion for the foreseeable future. A typical IPv6 address can be divided into two parts. As shown in the figure below, the first 64 bits are used to represent the network, and the next 64 bits are used as the interface identifier.
The interface identifier can be generated in several ways:
\nIETF recommends a canonical textual representation format for ease of writing. It includes leading zeros suppression and compression of consecutive all-zero fields. With the network prefix length at the end, the above address can be shortened to 2001:db8:130f::7000:0:140b/64.
\nRFC 4291 defines three types of addresses:
\nNote that there are no broadcast addresses in IPv6, their function being superseded by multicast addresses. Anycast addresses are syntactically indistinguishable from unicast addresses and have very limited applications. A typical application for anycast is to set up a DNS root server to allow hosts to look up domain names in close proximity. For unicast and multicast addresses, they can be identified by different network prefixes:
\nAddress Type | \nBinary Form | \nHexadecimal Form | \nApplication | \n
---|---|---|---|
Link-local address (unicast) | \n1111 1110 10 | \nfe80::/10 | \nUse on a single link, non-routable | \n
Unique local address (unicast) | \n1111 1101 | \nfd00::/8 | \nAnalogous to IPv4 private network addressing | \n
Global unicast address | \n001 | \n2000::/3 | \nInternet communications | \n
Multicast address | \n1111 1111 | \nff00::/8 | \nGroup communications, video streaming | \n
Each interface of a host must have a link-local address. Additionally, it can be manually or dynamically autoconfigured to obtain a unique local address and a global unicast address. Thus, IPv6 interfaces naturally have multiple unicast addresses. Unique local addresses are managed by the local network administrator, while the global unicast addresses are allocated by the IANA-designated regional registry. Referring to the following diagram, all current global unicast addresses are assigned from the 2000::/3 address block, with the first 48 bits of the address identifying the service provider's global routing network and the next 16 bits identifying the enterprise or campus internal subnet: Because an IPv6 multicast address can only be used as a destination address, its bit definition is different from that of unicast. Referring to RFC 4291, a multicast address containing 4 bits of the feature flags, 4 bits of the group scope, and the last 112 bits of the group identifier:
Furthermore the same protocol specifies a few pre-defined IPv6 multicast addresses, the most important of which are
IPv6 dynamic address assignment depends on Neighbor Discovery Protocol (NDP). NDP acts at the data link layer and is responsible for discovering other nodes and corresponding IPv6 addresses on the link and determining available routes and maintaining information reachability to other active nodes. It provides the IPv6 network with the equivalent of the Address Resolution Protocol (ARP) and ICMP router discovery and redirection protocols in IPv4 networks. However, NDP adds many improvements and new features. NDP defines five ICMPv6 message types:
\nThe first two message types here, RS and RA, are the keys to implementing dynamic IPv6 address assignment. The host sends an RS message to the multicast address ff02::2 of all routers in the local network segment to request routing information. When the router receives the RS from the network node, it sends an immediate RA in response. The message format of the RA is as follows
\n0 1 2 3 |
It defines two special bits, M and O, with the following meaning:
\nThe RA message ends with the Options section, which originally had three possible options: Source Link-Layer Address, MTU, and Prefix Information. Later, RFC 8106 (which replaced RFC 6106) added the Recursive DNS Server (RDNSS) and DNS Search List (DNSSL) options. The Prefix Information option directly provide hosts with on-link prefixes and prefixes for Address Autoconfiguration, and it has the following format
\n0 1 2 3 |
Here the Prefix Length and the Prefix jointly determine the network prefix of the IPv6 address. In addition, the Prefix Information option also defines two special bits, L and A:
\nSimilar to the IPv4 subnet mask feature, the purpose of the \"on-link\" determination is to allow the host to determine which networks an interface can access. By default, the host only considers the network where the link-local address is located as \"on-link\". If the \"on-link\" status of a destination address cannot be determined, the host forwards the IPv6 datagram to the default gateway (or default router) by default. When the host receives an RA message, if the \"on-link\" flag for a prefix information option is set to 1 and the Valid Lifetime is also a non-zero value, the host creates a new prefix network entry for it in the prefix list. All unexpired prefix network entries are \"on-link\".
\nAfter understanding the NDP protocol and the information conveyed by the RA messages, let's see how they guide the network nodes to achieve dynamic address assignment.
\nRouters in the network periodically send RA messages to the multicast addresses (ff02::1) of all nodes in the local subnet. However, to avoid latency, the host sends one or more RS messages to all routers in the local subnet as soon as it has finished booting. The protocol requires the routers to respond to the RA messages within 0.5 seconds. Then, based on the values of the M/O/A bits in the received RA messages, the host decides how to dynamically configure the unique local and global unicast addresses of the interface and how to obtain other configuration information. With certain combinations of bit fetch values, the host needs to run DHCPv6 client software to connect to the server to obtain address assignment and/or other configuration information. The entire process is shown in the following message sequence diagram.
\n\nsequenceDiagram\n\nparticipant R as Router\nparticipant H as Host\nparticipant S as DHCPv6 Server\nNote over R,H: Router Request\nrect rgb(239, 252, 202)\nH->>R: Router Solicitation\nR-->>H: Router Advertisement\nend\nNote over H,S: Address Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Solicit\nS-->>H: DHCPv6 Advertise\nH->>S: DHCPv6 Request\nS-->>H: DHCPv6 Reply\nend\nNote over H,S: Other Information Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Information-request\nS-->>H: DHCPv6 Reply\nend\n\n\n
Note: Unlike the IPv4 DHCP protocol, DHCPv6 clients use UDP port 546 and servers use UDP port 547.
\nNext explain in detail three dynamic allocation schemes determined by the combination of the M/O/A-bit values:
\nSLAAC is the simplest automatic IPv6 address assignment scheme and does not require any server. It works by sending an RS message request after the host starts up and the router sends back RA messages to all nodes in the local network segment. If the RA message contains the following configuration
\nThen the host receives this RA message and performs the following operations to implement SLAAC:
\nThis way, the host gets one or more IPv6 unique local addresses or global unicast addresses, plus the default gateway and domain name service information to complete various Internet connections.
\nThe following is an example of the SLAAC configuration on a Cisco Catalyst 9300 Multilayer Access Switch:
\nipv6 unicast-routing |
The Layer 3 interface of the Cisco Multilayer Switch provides routing functionality. As you can see, when IPv6 is activated on the Layer 3 interface in VLAN 10, its default address auto-assignment scheme is SLAAC. the control bits of RA messages from this interface are all set according to the SLAAC scheme, and the network prefixes for each IPv6 address it configures are automatically added to the RA prefix information options list. Of course, the network administrator can also exclude certain network prefixes with a separate interface configuration command. The last two lines of the example configuration command specify RDNSS and DNSSL, which are also added to the RA message options.
\nIf a host connects to a port in VLAN 10, it immediately gets a global unicast address with the network prefix of 2001:ABCD:1000::/64, and its default gateway address is set to 2001:ABCD:1000::1. Open a browser and enter a URL, and it will send a message to the specified domain name server 2001:4860:4860::8888 (Google's public name server address) to obtain the IPv6 address of the destination URL to establish a connection.
\nSLAAC automatic address assignment is fast and easy, providing a plug-and-play IPv6 deployment solution for small and medium-sized network deployments. However, if a network node needs access to additional configuration information, such as NTP/SNTP server, TFTP server, and SIP server addresses, or if its functionality relies on certain Vendor-specific Information Options, it must choose SLAAC + stateless DHCPv6 scheme.
\nThis scenario still uses SLAAC automatic address assignment, but the router instructs the host to connect to a DHCPv6 server for additional configuration information. At this point, the RA message sent back by the router has
\nAfter receiving this RA message, the host performs the following actions:
\nAs you can see, SLAAC + stateless DHCPv6 is not different from SLAAC in terms of address assignment. DHCPv6 only provides additional configuration information and does not assign IPv6 addresses. So the DHCPv6 server does not track the address assignment status of network nodes, which is what \"stateless\" means.
\nThe corresponding configuration commands on the Catalyst 9300 switch are as follows.
\nipv6 unicast-routing |
The difference with the SLAAC example is that the VLAN 10 interface configuration command ipv6 nd other-config-flag
explicitly specifies to set the O-bit of the RA message. Its next command, ipv6 dhcp server vlan-10-clients
, activates the DHCPv6 server response feature of the interface, corresponding to the server's pool name of vlan-10-clients
. The DHCPv6 server is configured above the interface configuration, starting at ipv6 dhcp pool vlan-10-clients
, and contains the DNS server address, DNS domain name, and SNTP server address.
If you are using a separate DHCPv6 server located on a network segment, you can remove the ipv6 dhcp server
command and enable the ipv6 dhcp relay destination
command on the next line of the example to specify the address to forward DHCPv6 requests to the external server.
Many large enterprises use DHCP to manage the IPv4 addresses of their devices, so deploying DHCPv6 to centrally assign and manage IPv6 addresses is a natural preference. This is where Stateful DHCPv6 comes into play. This scenario also requires RA messages sent by the router but does not rely solely on network prefixes for automatic address assignment. The control bits of the RA messages are configured to
\nUpon receiving this RA message, the host performs the following actions:
\nAn example of the Stateful DHCPv6 configuration command on a Catalyst 9300 switch is as follows.
\nipv6 unicast-routing |
Compared to SLAAC + Stateless DHCPv6, the interface configuration here removes the ipv6 nd other-config-flag
and replaces it with the ipv6 nd managed-config-flag
command. This corresponds to setting the M-bit of the RA message header. The DHCPv6 server configuration adds two address prefix
commands to set the network prefix. Also, the ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise
configured for the interface specifies that the router does not include the 2001:ABCD:1:1::/64 prefix information option into the RA. So, this example host interface will not generate SLAAC addresses, but only two addresses from DHPCv6: a unique local address with the network prefix FD09:9:5:90::/64, and a global unicast address with the network prefix 2001:9:5:90::/64. The interface identifier for each of these two addresses is also specified by DHPCv6.
How to distinguish the source of dynamically assigned addresses for host interfaces? The method is simple. One thing to remember is that DHPCv6 does not send the network prefix length to the requestor, so the network prefix length of the addresses received from DHPCv6 is 128, while the network prefix length of the addresses generated by SLAAC will not be 128. See the following example of the wired0 interface on a Linux host:
\nifconfig wired0 |
We can immediately determine that the interface is using Stateful DHCPv6 address assignment, but also generates the SLAAC address with the same network prefix 2001:20::/64 received.
\nNote: DHPCv6 server also does not provide any IPv6 default gateway information. The host needs to be informed of the dynamic default gateway from the RA message.
\nThe following table shows the control bit combinations of RA messages concerning different address allocation and other configuration acquisition methods.
\nM-bit | \nO-bit | \nA-bit | \nHost Address | \nOther Configuration | \n
---|---|---|---|---|
0 | \n0 | \n0 | \nStatic Settings | \nManual Configuration | \n
0 | \n0 | \n1 | \nPrefix specified by RA, automatically generated | \nmanually configured | \n
0 | \n1 | \n0 | \nStatic Settings | \nDHCPv6 | \n
0 | \n1 | \n1 | \nPrefix specified by RA, automatically generated | \nDHCPv6 | \n
1 | \n0 | \n0 | \nStateful DHCPv6 | \nDHCPv6 | \n
1 | \n0 | \n1 | \nStateful DHCPv6 and/or automatically generated | \nDHCPv6 | \n
1 | \n1 | \n0 | \nStateful DHCPv6 | \nDHCPv6 | \n
1 | \n1 | \n1 | \nStateful DHCPv6 and/or automatically generated | \nDHCPv6 | \n
Summarize three dynamic allocation schemes:
\nAllocation Scheme | \nFeatures | \nAppiccation Scenarios | \n
---|---|---|
SLAAC | \nSimple and practical, fast deployment | \nSMB, Consumer Product Networking, Internet of Things (IoT) | \n
SLAAC + Stateless DHCPv6 | \nAuto Configuration, Extended Services | \nSMBs need additional network services | \n
Stateful DHCPv6 | \nCentralized management and control | \nLarge enterprises, institutions, and campus networks | \n
Note: Since IPv6 network interfaces can have multiple addresses (a link-local address, plus one or more unique local addresses and/or global unicast addresses), it becomes important how the source address is selected when establishing an external connection. RFC 6724 gives detailed IPv6 source address selection rules. In the development of embedded systems, the control plane and the data plane connected to the same remote device are often implemented by different functional components. For example, the control plane directly calls a Linux userspace socket to establish the connection, and the IPv6 source address used for the connection is selected by the TCP/IP stack, while the data plane directly implements data encapsulation processing and transmission in kernel space. In this case, the IPv6 source address selected by the control plane has to be synchronized to the data plane in time, otherwise, the user data might not be delivered to the same destination.
\nThe common IPv6 dynamic address assignment debugging and troubleshooting commands on Cisco routers and switches are listed in the following table.
\nCommand | \nDescription | \n
---|---|
show ipv6 interface brief | \nDisplays a short summary of IPv6 status and configuration for each interface | \n
show ipv6 interface [type] [num] | \nDisplays IPv6 and NDP usability status information for single interface | \n
show ipv6 interface [type] [num] prefix | \nDisplays IPv6 network prefix information for single interface | \n
show ipv6 dhcp pool | \nDisplay DHCPv6 configuration pool information | \n
show ipv6 dhcp binding | \nDisplays all automatic client bindings from the DHCPv6 server binding table | \n
show ipv6 dhcp interface [type] [num] | \nDisplay DHCPv6 interface information | \n
debug ipv6 nd | \nDebug IPv6 NDP protocol | \n
debug ipv6 dhcp | \nDebug DHCPv6 server | \n
The following console NDP protocol debug log shows that the router received an RS message from host FE80::5850:6D61:1FB:EF3A and responded with an RA message to the multicast address FF02::1 of all nodes in this network:
\nRouter# debug ipv6 nd |
And the next log shows an example of Stateless DHCPv6 observed after entering the debug ipv6 dhcp
debug command. Host FE80::5850:6D61:1FB:EF3A sends an INFORMATION-REQUEST message to the DHCPv6 server, which selects the source address FE80::C801:B9FF:FEF0:8 and sends a response message.
Router#debug ipv6 dhcp |
The following debug log of Stateful DHCPv6 shows the complete process of two message exchanges (SOLICIT/ADVERTISE, REQUEST/REPLY) on lines 1, 15, 16, and 26.
\nIPv6 DHCP: Received SOLICIT from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0 |
For complex cases where it is difficult to identify whether the problem is with the host, router, or DHCPv6 server, we recommend using the free open-source network packet analysis software Wireshark to capture packets of the entire process for analysis. While analyzing packets with Wireshark, you can apply the keyword filtering function.
\nFilter String | \nOnly Show | \n
---|---|
icmpv6.type=133 | \nICMPv6 RS | \n
icmpv6.nd.ra.flag | \nICMPv6 RA | \n
dhcpv6 | \nDHCPv6 packets | \n
We can either run Wireshark directly on the host side, or we can use the Switched Port Analyzer (SPAN) provided with the switch. Running on the network side, SPAN can collectively redirect packets from a given port to the monitor port running Wireshark for capturing. Cisco Catalyst 9300 Series switches also directly integrate with Wireshark software to intercept and analyze filtered packets online, making it very easy to use.
\nSample packet capture files for three allocation scheme are available here for download and study: slaac.pcap,stateless-dhcpv6.pcap,stateful-dhcpv6.pcap
\nAccurate and effective testing of IPv6 products is key to ensuring high interoperability, security, and reliability of IPv6 infrastructure deployments. The IPv6 Ready logo is an IPv6 testing and certification program created by the IPv6 Forum. Its goals are to define IPv6 conformance and interoperability test specifications, provide a self-testing toolset, establish Global IPv6 Test Centers and provide product validation services, and finally, issue IPv6 Ready logo.
\nIn May 2020, IPv6 Ready Logo Program published new version 5.0 test specifications:
\nAlong with these two new test specifications, the project team also affirmed two permanent changes:
\nNot surprisingly, the new version 5.0 core protocols test specification has a section dedicated to defining SLAAC test cases to validate this core IPv6 protocol.
\nIn the list below, the RFCs shown in bold are directly covered by the IPv6 Ready Version 5.0 Core Protocol Test Specification:
\nPurdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 1 exam.
\nBelow are extracted from the Spring 2024 CS24000 course syllabus:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) gcc -Wall -Werror -g -c abc.c -o xyz.o
\nExplanation of the options used:
-Wall
: Enable all warnings.-Werror
: Treat warnings as errors.-g
: Include debugging information in the output file.-c
: Compile or assemble the source files, but do not link.abc.c
: The source file to be compiled.-o xyz.o
: Specify the output file name (xyz.o).📝Notes: This output file xyz.o
is not executable since it is just the object file for a single c source file. We need to link to the standard library to make a executable file. If we force to run this xyz.o, it will return something like exec format error
.
(b) gcc xyz.o abc.o def.c -o prog
\nExplanation:
xyz.o
, abc.o
: Object files to be linked.def.c
: Source file to be compiled and linked.-o prog
: Specify the output file name (prog).(c) It advises gcc to include all warnings that help detect potentially problematic code.
(d) Many functions found in the string library (declared in string.h
) rely on null-terminated strings to operate correctly. Null-terminated strings are sequences of characters followed by a null character ('\\0'), which indicates the end of the string. Functions like strlen
, strcpy
, strcat
, strcmp
, and others expect null-terminated strings as input and produce null-terminated strings as output.
(e) In C, memory for a variable is allocated during its definition, not during its declaration.
\nDeclaration is announcing the properties of variable (no memory allocation), definition is allocating storages for a variable. Put pure declaration (struct, func prototype, extern) outside of the func, put definition inside func.
(f) size = 32
(There are 8 integer elements in this array, so 4 * 8.)
(g) 5 (Because ptr
is given the address of the 3rd element. So *(ptr - 1)
is the value of the 2nd element.)
(h) 12 (This is equal to *(ptr - *(ptr + 3))
, then *(ptr - 2)
. So finally it points to the 1st element of the array.)
(i) 8 (Because it mentions \"64-bit architecture\", so all addresses are of size 64-bit)
(a) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')
\n
struct resistor {
char id[ID_LEN];
float max_power;
int resistance;
};
(b) The answer is shown below:
\ntypedef struct resistor resistor_t;
(c) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')
\n
struct circuit_struct {
char name[CNAME_LEN];
resistor_t resistors[10];
};
(d) It will print sizeof = 920
. Explanation: 5 * (24 + 10 * (8 + 4 + 4)) = 920. This is because the id inside the resistor will occupy 8 bytes after padding to a multiple of 4.
struct circuit_struct circuit_board[5];
(e) The function can be written like the following:
\nint find_voltage(resistor_t r, int c) {
return (c * r.resistance);
}
The complete program is shown below
\n
|
The solution can be like this: (the include and struct definition are not necessary)
\n
|
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Midterm exams.
\nBelow are extracted from the Summer 2023 CS24000 course homepage:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Consider the code snippet
\nint a, *b, *c; |
Explain in detail what is likely to happen if the code snippet is compiled and executed.
\n(b) What are the possible outcomes if the code snippet
\nchar r[4]; |
is compiled and executed? Explain your reasoning.
\n(c) Suppose we have a 2-D array, int x[2][3]
, wherein 6 integers are stored. What array expression is *(*(x+1)+2)
equivalent to, and why?
Problem 1 Solution
\n(a) The first printf()
outputs 3 since b
is a pointer to integer variable a. *c = 5
is likely to generate a segmentation fault since the code does not place a valid address in c before this assignment. The second printf()
is likely not reached due to a segmentation fault from *c = 5
which terminates the running program.
(b) There are two possible outcomes:
\nExplanation: If the memory location r[2]
contains EOS ('\\0') then the first outcome results. Otherwise, printf()
will continue to print byte values (not necessarily ASCII) until a byte containing 0 (i.e.,EOS) is reached.
(c) Equivalent to x[1][2]
.
Explanation: In our logical view of 2-D arrays: x
points to the location in memory where the beginning addresses of two 1-D integer arrays are located. Therefore x+1
points to the beginning address of the second 1-D integer array. *(x+1)
follows the pointer to the beginning address of the second 1-D integer array. *(x+1)+2
results in the address at which the third element of the second 1-D integer array is stored. *(*(x+1)+2)
accesses the content of the third element of the second 1-D integer array. Hence equivalent to x[1][2]
.
(a) Suppose main()
calls function
int abc(void) { |
three times. Explain what values are returned to main() in each of the three calls to abc()
.
(b) Suppose the code snippet
\nfloat m, **n; |
is compiled and executed. What is likely to happen, and why? How would you modify the code (involving printf()
calls) to facilitate ease of run-time debugging?
Problem 2 Solution
\n(a) Here are the three return values for each call and the explanation:
\na++
returns 4 before incrementing a
.b
becomes 2 since it preserves the previous value from the first call. So the if-statement checks 4 > 3. Hence a++
returns 4.b
becomes 3 at the beginning of the call, and the if-statement checks 4 > 4. So the program goes to the else-part which increments b
again and returns b
. Hence the function call returns 5.(b) Since we did not assign a valid address to n
, **n
is likely to reference an invalid address that triggers a segmentation fault which terminates the running program.
Although the first printf()
call was successful, 3.3 will likely will not be output to stdout (i.e., display) due to abnormal termination of the program and buffering by stdio library functions.
Adding a newline in the first printf(
) call, or calling fflush(stdout)
after the first printf()
call will force 3.3 in the stdout buffer to be flushed before the program terminates due to segmentation fault.
(a) Suppose you are supervising a team of C programmers. One of the team members is responsible for coding a function, int readpasswd(void)
, that reads from stdin a new password and checks that it contains upper case letters, special characters, etc. per company policy. The team member shows you part of the code
int readpasswd() { |
that reads a password from stdin and stores it in local variable secret
for further processing. Explain why you would be alarmed by the code. How would you rewrite to fix the problem in the code?
(b) Code main()
that reads a file, test.out, byte by byte using fgetc()
and counts how many bytes are ASCII characters. main()
outputs the count to stdout. Focus on making sure that your code is robust and does not crash unexpectedly.
Problem 3 Solution
\n(a) The scanf()
does not prevent user input that exceeds buffer size (100 characters) from overwriting memory in readpasswd()
's stack frame, potentially modifying its return address. This can lead to the execution of unintended code such as malware.
Alternate: The scanf()
functions can lead to a buffer overflow if used improperly. Here in this function, it does not have bound checking capability and if the input string is longer than 100 characters, then the input will overflow into the adjoining memory and corrupt the stack frame.
📝Notes: This is a major security flaw in scanf
family (scanf
, sscanf
, fscanf
..etc) esp when reading a string because they don't take the length of the buffer (into which they are reading) into account.
To fix this, the code should explicitly check that no more than 100 characters are read from stdin to prevent overflow over secret[100]
. This can be done by reading character by character using getchar()
in a loop until a newline is encountered or 100 characters have been read.
(b) A sample solution can be seen below
\n
|
Suppose you are given the code in main.c
\nint s[5]; |
which is compiled using gcc and executed. What are the two possible outcomes? Explain your answer.
\nBonus Problem Solution
\ns[5]
which may, or may not, corrupt program data and computation but does not crash the running program (i.e., silent run-time bug).s[5]
which exceeds the running program's valid memory, resulting in a segmentation fault.(a) Consider the code snippet
\nint x, *y, *z; |
Explain what is likely to happen if the code snippet is compiled and executed as part of main()
.
(b) Explain what the declarations of g and h mean:
\nchar *g(char *), (*h)(char *); |
For the two assignment statements to be meaningful
\nx = g(s); |
what must be the types of x
and y
? Provide the C statements for their type declarations.
Problem 1 Solution
\n(a) printf()
will output 10 (for x) and the address of x (in hexadecimal notation) which is contained in y. Assignment statement *z = 3
will likely trigger a segmentation fault since a valid address has not been stored in z.
(b) g is a function that takes a single argument that is a pointer to char (i.e., char *
), and g returns a pointer to char (i.e., address that points to char). h is a function pointer that takes a single argument that is a pointer to char, and h returns a value of type char.
x is a pointer to char, i.e., char *x
. y is a function that takes an argument that is a pointer to char and returns a value of type char, i.e., char y(char *)
.
(a) For the function
\nvoid fun(float a) { |
explain what is likely to happen if fun()
is called by main()
. Explain how things change if 1-D array x
is made to be global.
(b) What are potential issues associated with code snippet
\nFILE *f; |
Provide modified code that fixes the issues.
\nProblem 2 Solution
\n(a) Calling fun() will likely generate a stack smashing error. This is so since x is local to fun()
and overflowing the 1-D array (by 3 elements, i.e., 12 bytes) is likely to cause the canary (bit pattern) inserted by gcc (to guard the return address) to be changed. If x is made global, gcc does not insert a canary, hence stack smashing will not occur. However, overflowing x may, or may not, trigger a segmentation fault.
(b) Two potential issues:
\nfopen()
may fail and return NULL.fscanf()
may overflow 1-D array r if the character sequence in data.dat
exceeds 100 bytes.To fix these, do the following modifications:
\nf = fopen("data.dat", "r"); |
(a) A 2-D integer array, int d[100][200]
, declaration is restrictive in that it hardcodes the number of rows and columns to fixed values 100 and 200, respectively. Suppose two integers N and M are read from stdin that specify the number of rows and columns of a 2-D integer array which is then used to read N x M integers from stdin into main memory. Provide C code main()
that uses malloc() to achieve this task. Your code should be complete but for including header files.
(b) Provide code that reads a value of type unsigned int
from stdin, then uses bit processing techniques to count how many of the 32 bits contain bit value 0. Annotate your code to note what the different parts are doing.
Problem 3 Solution
\n(a) The complete code is shown below (Note we skip the NULL check for the return of malloc()
, add that after each such call if required)
int main() { |
📝Notes: Freeing memory of such a 2-D integer array also needs two steps:
\nvoid free_2d_array(int **array, int rows) { |
(b) The solution code can be seen below
\nunsigned int x, m = 1; |
Explain why printf(\"%d\", x)
passes argument x by value whereas scanf(\"%d\", &x)
passes the argument by reference. Can one code printf()
so that it passes x by reference? If so, why is it not done?
Bonus Problem Solution
\nprintf()
only needs a copy of the value of x to do its work of printing the value to stdout. scanf()
needs the address of x so that the value entered through stdin (by default, keyboard) can be stored at the address of x. Yes, since following the address of x allows printf() to access its value. It is not necessary to reveal the address of x to printf()
since it only requires its value.
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Final exams.
\nBelow are extracted from the Summer 2023 CS24000 course homepage:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Which statements in the code
\ntypedef struct friend { |
are problematic, likely to trigger segmentation fault? Augment the code by adding calls to malloc()
so that the bugs are fixed.
(b) Explain the difference between fun1 and fun2 which are declared as char *fun1(char *)
and char (*fun2)(char *)
, respectively. Code a function fun3 that takes a string as argument and returns the last character of the string. You may assume that the string is of length at least 1 (not counting EOS).
(c) Suppose a user enters the command, %/bin/cp file1 file2
, using a shell to copy the content of file1 to file2 on one of our lab machines. From the viewpoint of the shell, from where does it read its input /bin/cp file1 file2
? From the viewpoint of the app /bin/cp
which is coded in C, how does it access its input which specify the names of two files whose content is to be copied? Before calling execv()
what must the shell do to prepare the arguments of execv()
so that /bin/cp
has access to the two file names?
Problem 1 Solution
\n(a) Problematic:
\namigo->year = 2017; |
The reason is that the pointer amigo
has not been initialized to the address of any allocated memory space yet.
Agumentation:
\namigo = (friend_t *)malloc(sizeof(friend_t)); |
(b) fun1 takes as argument a pointer to char and returns a pointer to char. fun2 is a function pointer to a function that takes as argument a pointer to char and returns a value of type char.
\nchar fun3(char *s) { |
(c) Input /bin/cp file1 file2
is read from stdin.
main(int argc, char *argv)
of /bin/cp
accesses the two file names via argv[1]
and argv[2]
.
Assuming a variable s
is of type, char **s
, a shell must allocate sufficient memory for s
and copy /bin/cp
into s[0]1
, file1
into s[1]
, file2
into s[2]
, and set s[3]
to NULL.
(a) Code a function, unsigned int countdbl(long)
, that takes a number of type long
as input, counts the number of 0s in the bit representation of the input, and returns 0 if the count is an even number, 1 if odd. Use bit processing techniques to solve the problem. (b) gcc on our lab machine, by default, will insert code to detect stack smashing at run-time. What does gcc's code try to prevent from happening? In the case of reading input from stdin
(or file), what is a common scenario and programming mistake that can lead to stack smashing? Provide an example using scanf()
(or fscanf()
). What issound programming practice that prevents stack smashing?
Problem 2 Solution
\n(a)
\nunsigned int countdbl(long x) { |
(b) When a function is called by another function, gcc tries to detect if the return address has been corrupted and, if so, terminate the running program.This is to prevent the code from jumping to unintended code such as malware.A local variable of a function declared as a 1-D array overflows by input whose length is not checked when reading from stdin (or file).Example: a function contains code
\nchar buf[100]; |
which may overflow buf[]
since scanf()
does not check for length of the input.Sound practice: use functions to read from stdin (or file) that check for length.In the above example use fgets()
instead of scanf()
.
Code a function that takes variable number of arguments, double multnums(char *, ...)
, multiplies them and returns the result as a value of type double
. The fixed argument is a string that specifies how many arguments follow and their type (integer 'd' or float 'f'). For example, in the call multnums(\"dffd\", 3, 88.2, -100.5, 44)
, the format string \"dffd\" specifies that four arguments follow where the first character 'd' means the first argument in the variable argument list is of type integer, the second and third 'f' of type float, and the fourth 'd' of type integer. Forgo checking for errors and ignore header files. What would happen in your code if multnums
is called as multnums(\"dffd\", 3, 88.2, -100.5, 44, -92, 65)
? What about multnums(\"dffd\", 3, 88.2, -100.5)?
Explain your reasoning.
Problem 3 Solution
\ndouble multnums(char *a, ...) { |
When a C function is defined with a variable number of arguments, it typically uses the va_arg
, va_start
, and va_end
macros from the <stdarg.h>
header to handle the variable arguments.
If the input argument count does not match the format string provided to functions like printf
or scanf
, it can lead to undefined behavior and potentially cause crashes, memory corruption, or incorrect output/input.
Here are some specific scenarios that can occur when there is a mismatch between the input arguments and the format string:
\nSuppose an ASCII file contains lines where each line is a sequence of characters ending with \\n
but for the last line which ends because the end of file is reached. The goal of main() is to read and store the lines of the ASCII into a variable, char **x
, where malloc()
is used to allocate just enough memory to store the content of the file. Using only basic file I/O operations discussed in class, describe in words how your code would work to accomplish this task. Be detailed in how the arguments of malloc()
are determined to store the file content in x
.
Bonus Problem Solution
\nmalloc()
to allocate 1-D array, int *M
, of size r of type int. Open file, read byte by byte, counting for each line the number of bytes. Store the line lengthin 1-D array M. Close file.malloc()
for each line to allocate memory to store the bytes of each line. Point x to the 1-D array of pointers to char.A sample implementation (not required for this exam) is shown as below:
\n
|
Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 2 exam.
\nBelow are extracted from the Spring 2024 CS24000 course syllabus:
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
(a) Code without using array brackets:
\nint reverse(int *source, int *dest, int n) { |
In summary, the reverse function reverses the order of elements in the source array, stores them in the dest array, and calculates the sum of the reversed elements.
\n(b) The atomic weight of Aluminum is 26.981.
\n(c) Structure for a singly-linked list node containing an integer:
\ntypedef struct single_node { |
(d) Function to prepend a node to a singly-linked list:
\nvoid push(single_node_t **head, single_node_t *node) { |
(e) Function to remove the first node from a singly-linked list:
\nsingle_node_t *pop(single_node_t **head) { |
(a) Structure for a doubly-linked list node containing a string and an integer:
\ntypedef struct double_node { |
(b) Function to create a new doubly-linked list node:
\ndouble_node_t *create(char *name, int age) { |
(c) Function to delete a node from a doubly-linked list:
\nvoid delete(double_node_t *node) { |
(d) Function to insert a new node after a given node in a doubly-linked list:
\nvoid insert(double_node_t *node, double_node_t *new_node) { |
(a) Structure for a binary tree node:
\ntypedef struct tree_node { |
(b) The size of the tree_node_t structure on a 64-bit architecture system is 24 bytes (4 bytes for int, 1 byte for bool, and 8 bytes for each pointer).
\n(c) Function to mark a node as invalid:
\nvoid delete_node(tree_node_t *node) { |
(d) Function to remove a node from a binary tree (assuming it's not the root):
\nvoid free_node(tree_node_t *node) { |
(e) Recursive function to delete invalid nodes from a binary tree:
\nint flush_tree(tree_node_t *root, void (*my_del)(tree_node_t *)) { |
Linear algebra provides mathematical tools to represent and analyze data and models in higher dimensions. It is essential for machine learning, computer graphics, control theory, and other scientific and engineering fields. Starting from this post, I will provide study guides and solutions to Purdue MA26500 exams in the last few semesters.
\nYou can’t learn too much linear algebra
— Benedict Gross (American mathematician, professor at the University of California San Diego and Harvard University, member of the National Academy of Sciences)
Purdue University is a world-renowned public research university that advances scientific, technological, engineering, and math discoveries. Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, as it is mandatory for undergraduate students of many science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
Let \\(A=\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\\),\\(B=\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}\\), and \\(C=AB^{-1}= \\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\\), then \\(a+b+c+d=\\)
\nProblem 1 Solution
\nBecause \\(C=AB^{-1}\\), we can multiple both sides by \\(B\\) and obtain \\(CB=AB^{-1}B=A\\). So \\[\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\n\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] Further, compute at the left side \\[\n\\begin{bmatrix}3a+4b & a+b\\\\3c+4d & c+d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] From here we can directly see \\(a+b=2\\) and \\(c+d=5\\), so \\(a+b+c+d=7\\). The answer is C.
\n⚠️Alert: There is no need to find the inverse of the matrix \\(B\\) and multiply the result with \\(A\\). Even if you can deduce the same answer, it is very inefficient and takes too much time.
\n\nLet \\(\\mathrm L\\) be a linear transformation from \\(\\mathbb R^3\\) to \\(\\mathbb R^3\\) whose standard matrix is \\(\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\2 &3 & k\\\\\\end{bmatrix}\\) where \\(k\\) is a real number. Find all values of \\(k\\) such that \\(\\mathrm L\\) is one-to-one.
\nProblem 2 Solution
\nFor this standard matrix, do elementary row operations below to achieve row echelon form.
\nFirst, add -2 times row 1 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &-1 &k-6\\\\\\end{bmatrix}\\] Then add row 2 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &0 &k-5\\\\\\end{bmatrix}\\] If \\(k=5\\), the equation \\(A\\mathbf x=\\mathbf b\\) has a free variable \\(x_3\\) and each \\(\\mathbf b\\) is the image of more than one \\(\\mathbf x\\). That is, \\(\\mathrm L\\) is not one-to-one. So the answer is E.
\n\nWhich of the following statements is/are always TRUE?
\nIf \\(A\\) is a singular \\(8\\times 8\\) matrix, then its last column must be a linear combination of the first seven columns.
Let \\(A\\) be a \\(5\\times 7\\) matrix such that \\(A\\cdot\\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\), and let \\(B\\) be a \\(7\\times 11\\) matrix such that \\(B\\cdot\\pmb x=\\pmb c\\) is consistent for any \\(\\pmb{c}∈\\mathbb{R}^7\\). Then, the matrix equation \\(AB\\cdot \\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\).
For any \\(m\\times n\\) matrix \\(A\\), the dimension of the null space of \\(A\\) equals the dimension of the null space of its transpose \\(A^T\\).
If \\(A\\) is an \\(m\\times n\\) matrix, then the set \\({A\\cdot\\pmb x|\\pmb x∈\\mathbb{R}^n}\\) is a subspace of \\(\\mathbb{R}^m\\).
Problem 3 Solution
\nFor (i), a singular matrix \\(A\\) is noninvertible and has \\(det(A)=0\\). By Theorem 8 of Section 2.3, the columns of \\(A\\) form a linearly dependent set. Denote \\(A=[\\pmb{v}_1\\cdots\\pmb{v}_8]\\), then there exist weights \\(c_1, c_2,\\cdots,c_8\\), not all zero, such that \\[c_1\\pmb{v}_1+c_2\\pmb{v}_2+\\cdots+c_8\\pmb{v}_8=\\pmb{0}\\] Does this imply that statement (i) is true? No! If \\(c_8\\) is 0, \\(\\pmb{v}_8\\) is NOT a linear combination of the columns \\(\\pmb{v}_1\\) to \\(\\pmb{v}_7\\).
\nFor (ii), since \\(AB\\cdot\\pmb x=A(B\\pmb{x})=A\\pmb c=\\pmb b\\). the consistency holds for the new \\(5\\times 11\\) matrix \\(AB\\) as well. It is true.
\nFor (iii), since \\(A\\) is a \\(m\\times n\\) matrix, \\(A^T\\) is a \\(n\\times m\\) matrix. From Section 2.9 Dimension and Rank, we know that \"If a matrix \\(A\\) has \\(n\\) columns, then \\(\\mathrm rank\\,A+\\mathrm{dim\\,Nul}\\,A= n\\).\" From this, we can list \\[\\begin{align}\n\\mathrm{dim\\,Nul}\\,A&=n-rank\\,A\\\\\n\\mathrm{dim\\,Nul}\\,A^T&=m-rank\\,A^T\n\\end{align}\\] As these two dimension numbers are not necessarily the same, (iii) is not true.
\nFor (iv), we can first review the definition of subspace. From Section 2.8 Subspaces of \\(\\mathbb R^n\\),
\n\n\nA subspace of \\(\\mathbb R^n\\) is any set \\(H\\) in \\(\\mathbb R^n\\) that has three properties:
\n
\na. The zero vector is in \\(H\\).
\nb. For each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u+\\pmb v\\) is in \\(H\\).
\nc. For each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in H.
Denote \\(\\pmb u=A\\pmb x\\), \\(\\pmb v=A\\pmb y\\), we have \\[\\begin{align}\nA\\cdot\\pmb{0}&=\\pmb{0}\\\\\n\\pmb u+\\pmb v&=A\\pmb{x}+A\\pmb{y}=A(\\pmb{x}+\\pmb{y})\\\\\nc\\pmb u&=cA\\pmb{x}=A(c\\pmb x)\n\\end{align}\\] All the results on the right side are in the set as well. This proves that (iv) is true.
\nAs both (ii) and (iv) are true, the answer is D.
\n\nCompute the determinant of the given matrix \\(\\begin{bmatrix}5 &7 &2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{bmatrix}\\)
\nProblem 4 Solution
\nNotice that the third column of the given matrix has all entries equal to zero except \\(a_{13}\\). Taking advantage of this, we can do a cofactor expansion down the third column, then continue to do cofactor expansion with the \\(3\\times3\\) submatrix \\[\\begin{align}\n\\begin{vmatrix}5 &7 &\\color{fuchsia}2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{vmatrix}&=(-1)^{1+3}\\cdot{\\color{fuchsia}2}\\cdot\\begin{vmatrix}0 &3 &-4\\\\\\color{blue}{-5} &-8 &3\\\\0 &5 &-6\\\\\\end{vmatrix}\\\\\n&=2\\cdot(-1)^{2+1}\\cdot({\\color{blue}{-5}})\\begin{vmatrix}3 &-4\\\\5 &-6\\\\\\end{vmatrix}=20\n\\end{align}\\] So the answer is B.
\n📝Notes:This problem is directly taken from the textbook. It is the Practice Problem of Section 3.1 Introduction to Determinants.
\n\nWhich of the following statements is always TRUE
\nA. If \\(A\\) is an \\(n\\times n\\) matrix with all entries being positive, then \\(\\det(A)>0\\).
\nB. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices with \\(\\det(A)>0\\) and \\(\\det(B)>0\\), then also \\(\\det(A+B)>0\\).
\nC. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices such that \\(AB=0\\), then both \\(A\\) and \\(B\\) are singular.
\nD. If rows of an \\(n\\times n\\) matrix \\(A\\) are linearly independent, then \\(\\det(A^{T}A)>0\\).
\nE. If \\(A\\) is an \\(n\\times n\\) matrix with \\(A^2=I_n\\), then \\(\\det(A)=1\\).
\nProblem 5 Solution
\nLet's analyze the statements one by one.
\nA is false. It is trivial to find a \\(2\\times 2\\) example to disprove it, such as \\[\\begin{vmatrix}1 &2\\\\3 &4\\\\\\end{vmatrix}=1\\times 4-2\\times 3=-2\\]
For B, as stated in Section 3 Properties of Determinants \"\\(\\det(A+B)\\) is not equal to \\(\\det(A)+\\det(B)\\), in general\", this statement is not necessarily true. On the contrary, we can have a simple case like \\(A=\\begin{bmatrix}1 &0\\\\0 &1\\\\\\end{bmatrix}\\) and \\(B=\\begin{bmatrix}-1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A+B)=0\\).
C is also false since B could be a zero matrix. If that is the case, A is not necessarily singular.
For D, first with the linearly independent property, we can see \\(\\det(A)\\neq 0\\). Secondary, the multiplicative property gives \\(\\det(A^{T}A)=\\det(A^{T})\\det(A)=(\\det(A))^2\\). So it is true that \\(\\det(A^{T}A) > 0\\).
For E, from \\(A^2=I_n\\), we can deduce \\(\\det(A^{2})=(\\det(A))^2=1\\), so \\(\\det(A)=\\pm 1\\). For example, if \\(A=\\begin{bmatrix}1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A)=-1\\). This statement is false.
So we conclude that the answer is D.
\n\nLet \\(A=\\begin{bmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{bmatrix}\\) and let its inverse \\(A^{-1}=[b_{ij}]\\). Find \\(b_{12}\\)
\nProblem 6 Solution
\nAccording to Theorem 8 of Section 3.3, \\(A^{-1}=\\frac{\\large{1}}{\\large{\\mathrm{det}\\,A}}\\mathrm{adj}\\,A\\). Here the adjugate matrix \\(\\mathrm{adj}\\, A\\) is the transpose of the matrix of cofactors. Hence \\[b_{12}=\\frac{C_{21}}{\\mathrm{det}\\,A}\\]
\nFirst computer the cofactor \\[C_{21}=(-1)^{2+1}\\begin{vmatrix}2 &6\\\\8 &10\\end{vmatrix}=(-1)\\cdot(20-48)=28\\] Now computer the determinant efficiently with row operations (Theorem 3 of Section 3.2) for \\(A\\) \\[\n{\\mathrm{det}\\,A}=\n\\begin{vmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{vmatrix}=\n\\begin{vmatrix}1 &2 &6\\\\0 &2 &-9\\\\0 &2 &-8\\\\\\end{vmatrix}=\n\\begin{vmatrix}\\color{blue}1 &2 &6\\\\0 &\\color{blue}2 &-9\\\\0 &0 &\\color{blue}1\\\\\\end{vmatrix}=\\color{blue}1\\cdot\\color{blue}2\\cdot\\color{blue}1=2\n\\] So \\(C_{21}=28/2=14\\), the answer is A.
\n\nLet \\(\\pmb{v_1}=\\begin{bmatrix}1\\\\2\\\\5\\\\\\end{bmatrix}\\), \\(\\pmb{v_2}=\\begin{bmatrix}-2\\\\-3\\\\1\\\\\\end{bmatrix}\\) and \\(\\pmb{x}=\\begin{bmatrix}-4\\\\-5\\\\13\\\\\\end{bmatrix}\\), and \\(\\pmb{B}=\\{\\pmb{v_1},\\pmb{v_2}\\}\\). Then \\(\\pmb B\\) is a basis for \\(H=\\mathrm{span}\\{\\mathbf{v_1,v_2}\\}\\). Determine if \\(\\pmb x\\) is in \\(H\\), and if it is, find the coordinate vector of \\(\\pmb x\\) relative to B.
\nProblem 7 Solution
\nBy definition in Section 1.3, \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) is the collection of all vectors that can be written in the form \\(c_1\\mathbf{v_1}+c_2\\mathbf{v_2}\\) with \\(c_1,c_2\\) scalars. So asking whether a vector \\(\\pmb x\\) is in \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) amounts to asking whether the vector equation \\[c_1\\pmb{v_1}+c_2\\pmb{v_2}=\\pmb{x}\\] has a solution. To answer this, row reduce the augmented matrix \\([\\pmb{v_1}\\,\\pmb{v_2}\\,\\pmb{x}]\\): \\[\n\\begin{bmatrix}1 &-2 &-4\\\\2 &-3 &-5\\\\5 &1 &13\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &11 &33\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &0 &0\\\\\\end{bmatrix}\\sim\n\\] We have a unique solution \\(c_1=2\\), \\(c_2=3\\). So the answer is E.
\n📝Notes:This problem is similar to Example 6 of Section 1.3 Vector Equations.
\n\nLet \\(T: \\mathbb R^2\\to\\mathbb R^3\\) be the linear tranformation for which \\[\nT\\left(\\begin{bmatrix}1\\\\1\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}3\\\\2\\\\1\\\\\\end{bmatrix}\\quad \\mathrm{and}\\quad\nT\\left(\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}1\\\\0\\\\2\\\\\\end{bmatrix}.\n\\] (4 points)(1) Let \\(A\\) be the standard matrix of \\(T\\), find \\(A\\).
\n(2 points)(2) Find the image of the vector \\(\\pmb u=\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}\\).
\n(4 points)(3) Is the vector \\(\\pmb b=\\begin{bmatrix}0\\\\-2\\\\5\\\\\\end{bmatrix}\\) in the range of \\(T\\)? If so, find all the vectors \\(\\pmb x\\) in \\(\\mathbb R^2\\) such that \\(T(\\pmb x)=\\pmb b\\)
\nProblem 8 Solution
\nReferring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, we know that \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] So if we can find \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\), we obtain \\(A\\). Remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\]
\nWe can use this property to find \\(A\\). First, it is trivial to see that \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =2\\begin{bmatrix}1\\\\1\\end{bmatrix}-\\begin{bmatrix}1\\\\2\\end{bmatrix}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =-\\begin{bmatrix}1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\2\\end{bmatrix}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=2T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}5\\\\4\\\\0\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=-T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}-2\\\\-2\\\\1\\end{bmatrix}\n \\end{align}\\] So \\(A\\) is \\(\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\).
The image of the vector \\(\\pmb u\\) can be obtained by \\(A\\pmb u\\), the result is \\[A\\pmb u=\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}=\\begin{bmatrix}-1\\\\-2\\\\3\\\\\\end{bmatrix}\\]
This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\[\\begin{bmatrix}5 &-2 &0\\\\4 &-2 &-2\\\\0 &1 &5\\end{bmatrix}\\] This has unique solution \\(\\begin{bmatrix}2\\\\5\\\\\\end{bmatrix}\\). So the vector \\(\\pmb b\\) is in the span of \\(T\\).
Consider the linear system \\[\n\\begin{align}\nx + 2y +3z &= 2\\\\\ny+az &= -4\\\\\n2x+5y+a^{2}z &= a-3\n\\end{align}\n\\] (4 points)(1) Find a row echelon form for the augmented matrix of the system.
\n(2 points)(2) For which value(s) of \\(a\\) does this system have a infinite number of solutions?
\n(2 points)(3) For which value(s) of \\(a\\) does this system have no solution?
\n(2 points)(4) For which value(s) of \\(a\\) does this system have a unique solution?
\nProblem 9 Solution
\nThe augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\2 &5 &a^2 &a-3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &1 &a^2-6 &a-7\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &0 &a^2-a-6 &a-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a2-a-6\\).
Look at the last row of the row echelon form, we can write it as \\((a-3)(a+2)z=(a-3)\\). Obviously if \\(a=3\\), \\(z\\) can be any number. So this system has an infinite number of solutions when \\(a=3\\).
If \\(a=-2\\), the equation becomes \\(0\\cdot z=-5\\). This is impossible. So the system is inconsistent and has no solution when \\(a=-2\\).
If \\(a\\neq -2\\) and \\(a\\neq 3\\),\\(z=\\frac 1 {a+2}\\), we can deduce unique solution for this system
Let \\[\nA=\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\n\\]
\n(5 points)(1) Find the REDUCED row echelon form for the matrix \\(A\\).
\n(5 points)(2) Find a basis for the null space of \\(A\\)
\nProblem 10 Solution
\nThe row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix. \\[\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &-1 &1 &-1 &3\\\\0 &-2 &1 &0 &3\\\\\\end{bmatrix}\\sim\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &-1 &1 &-3\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\] \\[\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\]
Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".
\nNow write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}1 &0 &0 &1 &2 &0\\\\0 &1 &0 &-1 &0 &0\\\\0 &0 &1 &-2 &3 &0\\\\\\end{bmatrix}\\]
\nThe general solution is \\(x_1=-x_4-2x_5\\), \\(x_2=x_4\\), \\(x_3=2x_4-3x_5\\), with \\(x_4\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-x_4-2x_5\\\\x_4\\\\2x_4-3x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]
📝Notes:This problem is similar to Example 6 of Section 2.8 Subspaces of \\(\\mathbb R^n\\). Read the solution for that example to get a deep understanding of this problem. Also pay attention to Example 7, Example 8, Theorem 13, and the Warning below this theorem in the same section.
\n\n\n\nWarning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). The columns of an echelon form \\(B\\) are often not in the column space of \\(A\\).
\n
This test set focuses on the following points of linear algebra:
\nAs can be seen, it has a very decent coverage of the basic ideas of linear algebra. So this set of exam problems provides a good test of students' knowledge of linear algebra.
\nOne thing I would like to highlight for preparing for the first exam of linear algebra is to have a complete understanding of two aspects of matrix equations. It is like two profiles of one object. As can be seen in the following snapshot taken from the textbook, a matrix equation can represent a linear combination of its column vectors. From a different viewpoint, it is used to describe the transformation that maps a vector in one space to a new vector in the other space.
\nHere comes the solution and analysis for Purdue MA 26500 Fall 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nLet \\[A=\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\] Let \\(a\\) be the rank of \\(A\\) and \\(b\\) be the nullity of \\(A\\), find \\(5b-3a\\)
\nProblem 1 Solution
\nDo row reduction as follows:
\n\\[\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\0 &2 &2 &-2 &0\\\\0 &3 &3 &-3 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &0 &2 &0 &-1\\\\0 &\\color{fuchsia}1 &1 &-1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]
\nSo we have 2 pivots, the rank is 2 and the nullity is 3. This results in \\(5b-3a=5\\cdot 3-3\\cdot 2=9\\).
\nThe answer is C.
\n\nLet \\(\\pmb u=\\begin{bmatrix}2\\\\0\\\\1\\end{bmatrix}\\), \\(\\pmb v=\\begin{bmatrix}3\\\\1\\\\0\\end{bmatrix}\\), and \\(\\pmb w=\\begin{bmatrix}1\\\\-1\\\\c\\end{bmatrix}\\) where \\(c\\) is a real number. The set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) is a basis for \\(\\mathbb R^3\\) provided that \\(c\\) is not equal
\nProblem 2 Solution
\nFor set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) to be a basis for \\(\\mathbb R^3\\), the three vectors should be linearly independent. Let's create a matrix with these vectors as columns, then do row reduction like below \\[\n\\begin{bmatrix}2 &3 &1\\\\0 &1 &-1\\\\1 &0 &c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\2 &3 &1\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &3 &1-2c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &0 &4-2c\\end{bmatrix}\n\\]
\nAs can be seen, we need 3 pivots to make these column vectors linearly independent. If \\(c\\) is 2, the last row above has all-zero entries, there would be only 2 pivots. So C cannot be 2 for these three vectors to be linearly independent.
\nThe answer is B.
\n\nWhich of the following statements is always TRUE?
\nProblem 3 Solution
\nPer definitions in 5.1 \"Eigenvectors and Eigenvalues\":
\n\n\nAn eigenvector of an \\(n\\times n\\) matrix \\(A\\) is a nonzero vector \\(\\pmb x\\) such that \\(A\\pmb x=\\lambda\\pmb x\\) for some scalar \\(\\lambda\\). A scalar \\(\\lambda\\) is called an eigenvalue of \\(A\\) if there is a nontrivial solution \\(\\pmb x\\) of \\(A\\pmb x=\\lambda\\pmb x\\); such an \\(\\pmb x\\) is called an eigenvector corresponding to \\(\\lambda\\).
\n
Statement A is missing the \"nonzero\" keyword, so it is NOT always TRUE.
\nFor Statement B, given \\(A\\pmb v=2\\pmb v\\), we can obtain \\(A(\\pmb{-v})=2(\\pmb{-v})\\). The eigenvalue is still 2, not \\(-2\\). This statement is FALSE.
\nStatement C involves the definition of Similarity. Denote \\(P=B^{-1}AB\\), we have \\[BPB^{-1}=BB^{-1}ABB^{-1}=A\\] So \\(A\\) and \\(P\\) are similar. Similar matrices have the same eigenvalues (Theorem 4 in Section 5.2 \"The Characteristic Equation\"). Statement C is FALSE
\n\n\nThis can be proved easily, as seen below \\[\\begin{align}\n\\det (A-\\lambda I)&=\\det (BPB^{-1}-\\lambda I)=\\det (BPB^{-1}-\\lambda BB^{-1})\\\\\n &=\\det(B)\\det(P-\\lambda I)\\det(B^{-1})\\\\\n &=\\det(B)\\det(B^{-1})\\det(P-\\lambda I)\n\\end{align}\\] Since \\(\\det(B)\\det(B^{-1})=\\det(BB^{-1})=\\det I=1\\), we see that \\(\\det (A-\\lambda I)=\\det(P-\\lambda I)\\). ■
\n
For Statement D, given \\(A\\pmb x=\\lambda\\pmb x\\), we can do the following deduction \\[A^2\\pmb x=AA\\pmb x=A\\lambda\\pmb x=\\lambda A\\pmb x=\\lambda^2\\pmb x\\] So it is always TRUE that \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).
\nStatement E is FALSE. An eigenvalue \\(-5\\) means matrix \\(B-(-5)I\\) is not invertible since \\(\\det(B-(-5)I)=\\det(B+5I)=0\\). But the statement refers to a different matrix \\(B-5I\\).
\nThe answer is D.
\n\nLet \\(\\mathbb P_3\\) be the vector space of all polynomials of degree at most 3. Which of the following subsets are subspaces of \\(\\mathbb P_3\\)?
\nProblem 4 Solution
\nPer the definition of Subspace in Section 4.1 \"Vector Spaces and Subspaces\"
\n\n\nA subspace of a vector space \\(V\\) is a subset \\(H\\) of \\(V\\) that has three properties:
\n
\na. The zero vector of \\(V\\) is in \\(H\\).
\nb. \\(H\\) is closed under vector addition. That is, for each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u + \\pmb v\\) is in \\(H\\).
\nc. \\(H\\) is closed under multiplication by scalars. That is, for each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in \\(H\\).
So to be qualified as the subspace, the subset should have all the above three properties. Denote the polynomials as \\(p(x)=a_0+a_1x+a_2x^2+a_3x^3\\).
\n(i) Since \\(p(0)=p(1)\\), we have \\(a_0=a_0+a_1+a_2+a_3\\), so \\(a_1+a_2+a_3=0\\).
\nThis proves that set (i) is a subspace of \\(\\mathbb P_3\\).
(ii) From \\(p(0)p(1)=0\\), we can deduce that \\(a_0(a_0+a_1+a_2+a_3)=0\\). So any polynomial in this set should satisfy this condition.
\nThis proves that set (ii) is NOT a subspace of \\(\\mathbb P_3\\).
(iii) It is easy to tell that this set is NOT a subspace of \\(\\mathbb P_3\\). If we do multiplication by floating-point scalars, the new polynomial does not necessarily have an integer coefficient for each term and might not be in the same set.
So the answer is A.
\n\nConsider the differential equation \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}1 &3\\\\-2 &2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\].
\nThen the origin is
\nProblem 5 Solution
\nFirst, write the system as a matrix differential equation \\(\\pmb x'(t)=A\\pmb x(t)\\). We learn from Section 5.7 \"Applications to Differential Equations\" that each eigenvalue–eigenvector pair provides a solution.
\nNow let's find out the eigenvalues of \\(A\\). From \\(\\det (A-\\lambda I)=0\\), we have \\[\\begin{vmatrix}1-\\lambda &3\\\\-2 &2-\\lambda\\end{vmatrix}=\\lambda^2-3\\lambda+8=0\\] This only gives two complex numbers as eigenvalues \\[\\lambda=\\frac{3\\pm\\sqrt{23}i}{2}\\]
\nReferring to the Complex Eigenvalues discussion at the end of this section, \"the origin is called a spiral point of the dynamical system. The rotation is caused by the sine and cosine functions that arise from a complex eigenvalue\". Because the complex eigenvalues have a positive real part, the trajectories spiral outward.
\nSo the answer is D.
\n\n\n\nRefer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:
\n\n\n
\n\n \n\n\nEigenvalues \nTrajectories \n\n \n\\(\\lambda_1>0, \\lambda_2>0\\) \nRepeller/Source \n\n \n\\(\\lambda_1<0, \\lambda_2<0\\) \nAttactor/Sink \n\n \n\\(\\lambda_1<0, \\lambda_2>0\\) \nSaddle Point \n\n \n\\(\\lambda = a\\pm bi, a>0\\) \nSpiral (outward) Point \n\n \n\\(\\lambda = a\\pm bi, a<0\\) \nSpiral (inward) Point \n\n \n\n\\(\\lambda = \\pm bi\\) \nEllipses (circles if \\(b=1\\)) \n
Which of the following matrices are diagonalizable over the real numbers?
\nProblem 6 Solution
\nThis problem tests our knowledge of Theorem 6 of Section 5.3 \"Diagonalization\":
\n\n\nAn \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n
So let's find out the eigenvalues for each matrix:
\nNow we can see that (i), (iii), and (iv) have distinct eigenvalues, they are diagonalizable matrices.
\nSo the answer is C.
\n\nA real \\(2\\times 2\\) matrix \\(A\\) has an eigenvalue \\(\\lambda_1=2+i\\) with corresponding eigenvector \\(\\pmb v_1=\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\). Which of the following is the general REAL solution to the system of differential equations \\(\\pmb x'(t)=A\\pmb x(t)\\)
\nProblem 7 Solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. Hence we know that \\(\\lambda_2=2-i\\) and \\(\\pmb{v}_2=\\begin{bmatrix}3+i\\\\4-i\\end{bmatrix}\\). However, we do not need these two to find our solution here. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{(2+i)t}\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}(\\cos t+i\\sin t)\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}\\begin{bmatrix}(3\\cos t+\\sin t)+(3\\sin t-\\cos t)i\\\\(4\\cos t-\\sin t)+(4\\sin t+\\cos t)i\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+\nc_2 e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\]
\nSo the answer is E.
\n\nLet \\(T: M_{2\\times 2}\\to M_{2\\times 2}\\) be a linear map defined as \\(A\\mapsto A+A^T\\).
\n(2 points) (1) Find \\(T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})\\)
\n(4 points) (2) Find a basis for the range of \\(T\\).
\n(4 points) (3) Find a basis for the kernel of \\(T\\).
\nProblem 8 Solution
\nAs the mapping rule is \\(A\\mapsto A+A^T\\), we can directly write down the transformation as below \\[T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})=\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}+\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}^T=\\begin{bmatrix}2 &5\\\\5 &8\\end{bmatrix}\\]
If we denote the 4 entries of a \\(2\\times 2\\) matrix as \\(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\), the transformation can be written as \\[\\begin{align}\nT(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})\n&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}+\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}^T=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}\\\\\n&=2a\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}+(b+c)\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix}+2d\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\n\\end{align}\\] So the basis can be the set of three \\(3\\times 3\\) matrices like below \\[\n\\begin{Bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix},\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix},\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\\end{Bmatrix}\n\\]
The kernel (or null space) of such a \\(T\\) is the set of all \\(\\pmb u\\) in vector space \\(V\\) such that \\(T(\\pmb u)=\\pmb 0\\). Write this as \\[T(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\0 &0\\end{bmatrix}\\] This leads to \\(a=d=0\\) and \\(c=-b\\). So the original matrix \\(A\\) that satified this conditioncan be represented as \\(c\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\). This shows that \\(\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\) (or \\(\\begin{bmatrix}0 &-1\\\\1 &0\\end{bmatrix}\\)) is the basis for the null space of \\(T\\).
(6 points) (1) Find all the eigenvalues of matrix \\(A=\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}\\), and find a basis for the eigenspace corresponding to each of the eigenvalues.
\n(4 points) (2) Find an invertible matrix \\(P\\) and a diagonal matrix \\(D\\) such that \\[\n\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}=PDP^{-1}\n\\]
\nProblem 9 Solution
\n(4 points) (1) Find the eigenvalues and corresponding eigenvectors of the matrix \\[\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\]
\n(2 points) (2) Find a general solution to the system of differential equations \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\]
\n(4 points) (3) Let \\(\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\\) be a particular soilution to the initial value problem \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix},\n\\begin{bmatrix}x(0)\\\\y(0)\\end{bmatrix}=\\begin{bmatrix}3\\\\7\\end{bmatrix}.\n\\] Find \\(x(1)+y(1)\\).
\nProblem 10 Solution
\nHere is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \nBook Sections | \n
---|---|---|
1 | \nThe Rank Theorem | \n4.6 \"Rank\" | \n
2 | \nLinear dependence, Invertible Matrix Theorem | \n4.3 \"Linearly Independent Sets; Bases\", 4.6 \"Rank\" | \n
3 | \nEigenvectors and Eigenvalues | \n5.1 \"Eigenvectors and Eigenvalues\" | \n
4 | \nVector Spaces and Subspaces | \n4.1 \"Vector Spaces and Subspaces\" | \n
5 | \nEigenfunctions of the Differential Equation | \n5.7 \"Applications to Differential Equations\" | \n
6 | \nThe Diagonalization Theorem, Diagonalizing Matrices | \n5.3 \"Diagonalization\" | \n
7 | \nComplex Eigenvalues and Eigenvectors | \n5.5 \"Complex Eigenvalues\" | \n
8 | \nKernel and Range of a Linear Transformation | \n4.2 \"Null Spaces, Column Spaces, and Linear Transformations\" | \n
9 | \nEigenvalues, Basis for Eigenspace, Diagonalizing Matrices | \n5.1 \"Eigenvectors and Eigenvalues\", 5.3 \"Diagonalization\" | \n
10 | \nEigenvectors and Eigenvalues | \n5.1 \"Eigenvectors and Eigenvalues\", 5.7 \"Applications to Differential Equations\" | \n
Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices).
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 Final exam covers all the topics from Chapter 1 to Chapter 7 Sections 1 in the textbook. This is a two-hour comprehensive common final exam given during the final exam week. There are 25 multiple-choice questions on the final exam.
\nProblem 1 Solution
\nStart with the augmented matrix of the system, and do row reduction like the below
\n\\[\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\2&0&-2&14\\\\3&2&1&3a\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&-4&-8&3a-48\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&0&0&3a-30\\end{array}\\right]\n\\]
\nClearly, this system of equations is consistent when \\(a=10\\). So the answer is B.
\n\nProblem 2 Solution
\nFirst review the properties of determinants:
\n>Let A be a square matrix.
\n> a. If a multiple of one row of \\(A\\) is added to another row to produce a matrix \\(B\\), then \\(\\det B =\\det A\\).
\nb. If two rows of \\(A\\) are interchanged to produce \\(B\\), then \\(\\det B=-\\det A\\).
\nc. If one row of A is multiplied by \\(k\\) to produce B, then \\(\\det B=k\\cdot\\det A\\).
Also since \\(\\det A^T=\\det A\\), a row operation on \\(A^T\\) amounts to a column operation on \\(A\\). The above property is true for column operations as well.
\nWith these properties in mind, we can do the following
\n\\[\\begin{align}\n\\begin{vmatrix}d&2a&g+d\\\\e&2b&h+e\\\\f&2c&i+f\\end{vmatrix}\n&=2\\times \\begin{vmatrix}d&a&g+d\\\\e&b&h+e\\\\f&c&i+f\\end{vmatrix}=\n 2\\times \\begin{vmatrix}d&a&g\\\\e&b&h\\\\f&c&i\\end{vmatrix}=\n 2\\times (-1)\\times \\begin{vmatrix}a&d&g\\\\b&e&h\\\\c&f&i\\end{vmatrix}\\\\\n&=(-2)\\times \\begin{vmatrix}a&b&c\\\\d&e&f\\\\g&h&i\\end{vmatrix}=(-2)\\times 1=-2\n\\end{align}\\]
\nSo the answer is A.
\n\nProblem 3 Solution
\nDenote \\(A=BCB^{-1}\\), it can be seen that \\[\\det A=\\det (BCB^{-1})=\\det B\\det C\\det B^{-1}=\\det (BB^{-1})\\det C=\\det C\\]
\nThus we can directly write down the determinant calculation process like below (applying row operations) \\[\n\\begin{vmatrix}1&2&3\\\\1&4&5\\\\-1&3&7\\end{vmatrix}=\n\\begin{vmatrix}1&2&3\\\\0&2&2\\\\0&5&10\\end{vmatrix}=\n1\\times (-1)^{1+1}\\begin{vmatrix}2&2\\\\5&10\\end{vmatrix}=\n1\\times (2\\times 10-2\\times 5)=10\n\\]
\nSo the answer is B.
\n\nProblem 4 Solution
\nProblem 5 Solution
\nProblem 6 Solution
\nNote the trace of a square matrix \\(A\\) is the sum of the diagonal entries in A and is denoted by tr \\(A\\).
\nRemember the formula for inverse matrix \\[\nA^{-1}=\\frac{1}{\\det A}\\text{adj}\\;A=[b_{ij}]\\qquad\nb_{ij}=\\frac{C_{ji}}{\\det A}\\qquad C_{ji}=(-1)^{i+j}\\det A_{ji}\n\\] Where \\(\\text{adj}\\;A\\) is the adjugate of \\(A\\), \\(C_{ji}\\) is a cofactor of \\(A\\), and \\(A_{ji}\\) denotes the submatrix of \\(A\\) formed by deleting row \\(j\\) and column \\(i\\).
\nNow we can find the answer step-by-step:
\nCalculate the determinant of \\(A\\) \\[\n\\begin{vmatrix}1&2&7\\\\1&3&12\\\\2&5&20\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&1&6\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&0&1\\end{vmatrix}=1\n\\]
Calculate \\(b_{11}\\), \\(b_{22}\\), and \\(b_{33}\\) \\[\nb_{11}=\\frac{C_{11}}{1}=\\begin{vmatrix}3&12\\\\5&20\\end{vmatrix}=0\\\\\nb_{22}=\\frac{C_{22}}{1}=\\begin{vmatrix}1&7\\\\2&20\\end{vmatrix}=6\\\\\nb_{33}=\\frac{C_{33}}{1}=\\begin{vmatrix}1&2\\\\1&3\\end{vmatrix}=1\n\\]
Get the trace of \\(A^{-1}\\) \\[\\text{tr}\\;A^{-1}=b_{11}+b_{22}+b_{33}=0+6+1=7\\]
So the answer is C.
\n\nProblem 7 Solution
\nFirst do row reduction to get row echelon form of the matrix \\(A\\):
\n\\[\\begin{align}\n&\\begin{bmatrix}1&2&2&10&3\\\\2&4&1&11&5\\\\3&6&2&18&1\\end{bmatrix}\\sim\n\\begin{bmatrix}1&2&2&10&3\\\\0&0&-3&-9&-1\\\\0&0&-4&-12&-8\\end{bmatrix}\\sim\n\\begin{bmatrix}1&2&2&10&3\\\\0&0&3&9&1\\\\0&0&1&3&2\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1&2&2&10&3\\\\0&0&3&9&1\\\\0&0&3&9&6\\end{bmatrix}\n\\sim\\begin{bmatrix}\\color{fuchsia}{1}&2&2&10&3\\\\0&0&\\color{fuchsia}{3}&9&1\\\\0&0&0&0&\\color{fuchsia}{5}\\end{bmatrix}\n\\end{align}\\]
\nThis shows that there are 3 pivot elements and 3 corresponding pivot columns (from the original matrix \\(A\\)) shown below
\n\\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\2\\\\3\\end{bmatrix},\n\\begin{bmatrix}2\\\\1\\\\2\\end{bmatrix},\n\\begin{bmatrix}3\\\\5\\\\1\\end{bmatrix}\n\\end{Bmatrix}\\]
\nThese columns form a basis for \\(\\text{Col}\\;A\\). Now look at the statements A and E.
\nIn the statement A, the first vector equals the sum of the first two pivot columns above. In the statement E, the third vector equals the sum of the last two pivot columns above. So both are TRUE.
\nTo check the statements B, C, and D, we need to find the basis for \\(\\text{Nul}\\;A\\). From the row echelon form, it can be deduced that with \\(x_2\\) and \\(x_4\\) as free variable \\[\\begin{align}\nx_5&=0\\\\x_3&=-3x_4\\\\x_1&=-2x_2-2x_3-10x_4=-2x_2-4x_4\n\\end{align}\\] This leads to \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n\\begin{bmatrix}-2x_2-4x_4\\\\x_2\\\\-3x_4\\\\x_4\\\\0\\end{bmatrix}=\nx_2\\begin{bmatrix}-2\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+x_4\\begin{bmatrix}-4\\\\0\\\\-3\\\\1\\\\0\\end{bmatrix}\n\\]
\nSo the basis of \\(\\text{Nul}\\;A\\) is \\[\\begin{Bmatrix}\n\\begin{bmatrix}-2\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n\\begin{bmatrix}-4\\\\0\\\\-3\\\\1\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\]
\nThe statement B is TRUE because its first vector is the first column above scaled by 2, and its 2nd vector is just the 2nd column above scaled by -1.
\nFor statement D, its 1st vector is the same as the first column above, and the 2nd vector is just the sum of the two columns. It is TRUE as well.
\nThe statement B is FALSE since generating the 2nd vector with 3 and -2 coexisting is impossible.
\nSo the answer is C.
\n\nProblem 8 Solution
\nProblem 9 Solution
\nTo find the \\(\\text{Ker}(T)\\), need to find the set of \\(p(t)\\) such that \\(T(p(t))=0\\) \\[\nT(a_0+a_{1}t+a_{2}t^2)=a_{2}t^3=0 \\Rightarrow a_2=0\n\\] Thus \\(p(t)=a_0+a_{1}t\\), the basis is \\({1,t}\\).
\nSo the answer is A.
\n\nProblem 10 Solution
\nProblem 11 Solution
\nThe vector set can be regarded as a linear transformation, then we can do row reduction with it:
\n\\[\n\\begin{bmatrix}1&1&1&1&1\\\\-1&1&2&0&-2\\\\1&1&1&1&3\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1}&1&1&1&1\\\\0&\\color{fuchsia}{2}&3&1&-1\\\\0&0&0&0&\\color{fuchsia}{2}\\end{bmatrix}\n\\] So there are 3 pivot entries and the rank is 3. The pivot columns below form a basis for \\(H\\). \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\-1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\-2\\\\3\\end{bmatrix}\n\\end{Bmatrix}\\]
\nA is wrong as it has only 2 vectors and the rank is 2.
\nFor B, C, and D, their 3rd vectors can be generated with the linear combination of the first two vectors. So their ranks are also 2.
\nE is equivalent to the basis above. Its second vector can be generated like below \\[\n\\begin{bmatrix}1\\\\-1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}=\n\\begin{bmatrix}2\\\\0\\\\2\\end{bmatrix}=2\\times \\begin{bmatrix}1\\\\0\\\\1\\end{bmatrix}\n\\]
\nSo the answer is E.
\n\nProblem 12 Solution
\nNote this question asks which one is NOT in the subspace spanned by \\(\\pmb x\\) and \\(\\pmb y\\). A vector is in the subspace spanned by \\(\\pmb x\\) and \\(\\pmb y\\) if and only if it is a linear combination of \\(\\pmb x\\) and \\(\\pmb y\\). This also means that the augmented matrix \\([\\pmb x\\;\\pmb y \\mid \\pmb v]\\) has solutions.
\nLet's try vector from A. \\[\n\\left[\\begin{array}{cc|c}2&1&4\\\\3&2&2\\\\1&1&1\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&1&4\\\\3&2&2\\\\2&2&2\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&1&4\\\\1&0&0\\\\0&1&-2\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&0&6\\\\1&0&0\\\\0&1&-2\\end{array}\\right]\\sim\n\\] This gives inconsistent results for \\(x_1\\). This vector is NOT a linear combination of \\(\\pmb x\\) and \\(\\pmb y\\). We do not need to continue here.
\nSo the answer is A.
\n\nProblem 13 Solution
\nFor 2 radians counter-clockwise rotation, the transformation matrix is written as \\[A=\\begin{bmatrix}\\cos(2)&-\\sin(2)\\\\\\sin(2)&\\cos(2)\\end{bmatrix}\\] To find the eigenvalues of this \\(2\\times 2\\) matrix, need to solve the equation \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}\\cos(2)-\\lambda&\\sin(2)\\\\-\\sin(2)&\\cos(2)-\\lambda\\end{vmatrix}=\\lambda^2-2\\cos(2)+\\cos^2(2)+\\sin^2(2)=\\lambda^2-2\\cos(2)+1\n\\] Apply the quadratic formula, get the roots \\[\\lambda=\\frac{2\\cos(2)\\pm\\sqrt{4\\cos^2(2)-4}}{2}=\\cos(2)\\pm i\\sin(2)\\]
\nSo the answer is C.
\n\nProblem 14 Solution
\nProblem 15 Solution
\nProblem 16 Solution
\nProblem 17 Solution
\nProblem 18 Solution
\nRemember Problem 6 introduced the definition of trace, which is the sum of all diagonal entries of a matrix. Denote the \\(2\\times 2\\) as \\(A=\\begin{bmatrix}a&b\\\\c&d\\end{bmatrix}\\), then \\(\\text{tr}(A)=a+d=-2\\). Since \\(\\det A=11\\), it gives \\(ad-bc=11\\).
\nWith these in mind, we can do the eigenvalue calculation below \\[\n\\begin{vmatrix}a-\\lambda&b\\\\c&d-\\lambda\\end{vmatrix}=\\lambda^2-(a+d)\\lambda+ad-bc=\\lambda^2+2\\lambda+11=0\n\\] Apply the quadratic formula, get the roots \\[\\lambda=\\frac{-2\\pm\\sqrt{4-44}}{2}=-1\\pm i\\sqrt{10}\\]
\n\n\nRefer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:
\n\n\n
\n\n \n\n\nEigenvalues \nTrajectories \n\n \n\\(\\lambda_1>0, \\lambda_2>0\\) \nRepeller/Source \n\n \n\\(\\lambda_1<0, \\lambda_2<0\\) \nAttactor/Sink \n\n \n\\(\\lambda_1<0, \\lambda_2>0\\) \nSaddle Point \n\n \n\\(\\lambda = a\\pm bi, a>0\\) \nSpiral (outward) Point \n\n \n\\(\\lambda = a\\pm bi, a<0\\) \nSpiral (inward) Point \n\n \n\n\\(\\lambda = \\pm bi\\) \nEllipses (circles if \\(b=1\\)) \n
So the answer is C.
\n\nProblem 19 Solution
\nProblem 20 Solution
\nProblem 21 Solution
\nProblem 22 Solution
\nProblem 23 Solution
\nProblem 24 Solution
\nProblem 25 Solution
\n\nMA 265 Fall 2022 Final\n
\n\n\nMA 265 Sprint 2023 Final\n
\n\n\nMA 265 Fall 2019 Final\n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/","content":"Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nProblem 1 Solution
\nA From the following \\[c_1(\\pmb u+\\pmb v)+c_2(\\pmb v+\\pmb w)+c_3\\pmb w=c_1\\pmb u+(c_1+c_2)\\pmb v+(c_2+c_3)\\pmb w\\] it can be concluded that if \\(\\pmb u\\), \\(\\pmb v\\), and \\(\\pmb w\\) are linearly independent, it is always true that \\(\\pmb u+\\pmb v\\), \\(\\pmb v+\\pmb w\\), and \\(\\pmb w\\) are linearly independent. So this statement is always true.
\nB This is also true. If the number of vectors is greater than the number of entries (\\(n\\) here), the transformation matrix has more columns than rows. The column vectors are not linearly independent.
\nC This is always true per the definition of basis and spanning set.
\nD If the nullity of a \\(m\\times n\\) matrix \\(A\\) is zero, \\(rank A=n\\). This means there the column vectors form a linearly independent set, and there is one pivot in each column. However, this does not mean \\(A\\pmb x=\\pmb b\\) has a unique solution for every \\(\\pmb b\\). For example, see the following augmented matrix in row echelon form (after row reduction): \\[\n\\begin{bmatrix}1 &\\ast &\\ast &b_1\\\\0 &1 &\\ast &b_2\\\\0 &0 &1 &b_3\\\\0 &0 &0 &b_4\\end{bmatrix}\n\\] If \\(b_4\\) is not zero, the system is inconsistent and there is no solution. So this one is NOT always true.
\nE This is always true since the rank of a \\(m\\times n\\) matirx is always in the range of \\([0, n]\\).
\nSo the answer is D.
\n\nProblem 2 Solution
\nDenote \\(3\\times 3\\) matrix as \\(A=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\), then from the given condition we can get \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\\\\n\\implies&\\begin{bmatrix}a &b &c\\\\2d &2e &2f\\\\3g &3h &3i\\end{bmatrix}=\\begin{bmatrix}a &2b &3c\\\\d &2e &3f\\\\g &2h &3i\\end{bmatrix}\\\\\n\\implies&A=\\begin{bmatrix}a &0 &0\\\\0 &2e &0\\\\0 &0 &3i\\end{bmatrix}=a\\begin{bmatrix}1 &0 &0\\\\0 &0 &0\\\\0 &0 &0\\end{bmatrix}+\n2e\\begin{bmatrix}0 &0 &0\\\\0 &1 &0\\\\0 &0 &0\\end{bmatrix}+\n3i\\begin{bmatrix}0 &0 &0\\\\0 &0 &0\\\\0 &0 &1\\end{bmatrix}\n\\end{align}\\]
\nIt can be seen that there are three basis vectors for this subspace and the dimension is 3. The answer is A.
\nNotice the effects of left-multiplication and right-multiplication of a diagonal matrix.
\n\nProblem 3 Solution
\nFrom \\(\\det A-\\lambda I\\), it becomes \\[\\begin{align}\n\\begin{vmatrix}4-\\lambda &0 &0 &0\\\\-2 &-1-\\lambda &0 &0\\\\10 &-9 &6-\\lambda &a\\\\1 &5 &a &3-\\lambda\\end{vmatrix}\n&=(4-\\lambda)(-1-\\lambda)((6-\\lambda)(3-\\lambda)-a^2)\\\\\n&=(\\lambda-4)(\\lambda+1)(\\lambda^2-9\\lambda+18-a^2)\n\\end{align}\\]
\nSo if 2 is an eigenvalue for the above, the last multiplication item becomes \\((2^2-18+18-a^2)\\) that should be zero. So \\(a=\\pm 2\\).
\nThe answer is E.
\n\nProblem 4 Solution
\n(i) Referring to Theorem 4 in Section 5.2 \"The Characteristic Equation\" >If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).
\nSo this statement must be TRUE.
\n(ii) If the columns of \\(A\\) are linearly independent, \\(A\\pmb x=\\pmb 0\\) only has trivial solution and \\(A\\) is an invertible matrix. This also means \\(\\det A\\neq 0\\). From here, it must be TRUE that \\(\\det A-0 I\\neq 0\\). So 0 is NOT an eigenvalue of \\(A\\). This statement is FALSE.
\n(iii) A matrix \\(A\\) is said to be diagonalizable if it is similar to a diagonal matrix, which means that there exists an invertible matrix \\(P\\) such that \\(P^{-1}AP\\) is a diagonal matrix. In other words, \\(A\\) is diagonalizable if it has a linearly independent set of eigenvectors that can form a basis for the vector space.
\nHowever, the condition for diagonalizability does not require that all eigenvalues be nonzero. A matrix can be diagonalizable even if it has one or more zero eigenvalues. For example, consider the following matrix: \\[A=\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\n=\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\] This matrix has one nonzero eigenvalue (\\(λ = 1\\)) and one zero eigenvalue (\\(λ = 0\\)). However, it is diagonalizable with the identity matrix as \\(P\\) and \\(D=A\\).
\nSo this statement is FALSE.
\n(iv) Similar matrices have the same eigenvalues (with the same multiplicities). Hence \\(-\\lambda\\) is also an eigenvalue of \\(B\\). Then we have \\(B\\pmb x=-\\lambda\\pmb x\\). From this, \\[\nBB\\pmb x=B(-\\lambda)\\pmb x=(-\\lambda)B\\pmb x=(-\\lambda)(-\\lambda)\\pmb x=\\lambda^2\\pmb x\n\\] So \\(\\lambda^2\\) is an eigenvalue of \\(B^2\\). Following the same deduction, we can prove that \\(\\lambda^4\\) is an eigenvalue of \\(B^4\\). This statement is TRUE.
\n(v) Denote \\(A=PBP^{-1}\\). If \\(A\\) is diagonizible, then \\(A=QDQ^{-1}\\) for some diagonal matrix \\(D\\). Now we can also write down \\[B=P^{-1}AP=P^{-1}QDQ^{-1}P=(P^{-1}Q)D(P^{-1}Q)^{-1}\\] This proves that \\(B\\) is also diagonalizable. This statement is TRUE.
\nSince statements (ii) and (iii) are FALSE and the rest are TRUE, the answer is D.
\n\nProblem 5 Solution
\n(i) Obviously \\(x=y=z=0\\) does not satisfy \\(x+2y+3z=1\\), this subset is NOT a subspace of \\(\\mathbb R^3\\).
\n(ii) This subset is a subspace of \\(\\mathbb R^3\\) since it has all the three properties of subspace:
\n(iii) Here \\(p(t)=a_0+a_1t+a_2t^2+a_3t^3\\) and \\(a_3\\neq 0\\). This set does not include zero polynomial. Besides, if \\(p_1(t)=t^3+t\\) and \\(p_2(t)=-t^3+t\\), then \\(p_1(t)+p_2(t)=2t\\). This result is not a polynomial of degree 3. So this subset is NOT closed under vector addition and is NOT a subspace of \\(\\mathbb P_3\\).
\n(iv) The condition \\(p(2)=0\\) means \\(a_0+2a_1+4a_3+8a_3=0\\). It does include zero polynomial. It also satisfies the other two properties because \\[\\begin{align}\ncp(2)&=c(a_0+2a_1+4a_3+8a_3)=0\\\\\np_1(2)+p_2(2)&=(a_0+2a_1+4a_3+8a_3)+(b_0+2b_1+4b_3+8b_3)=0\n\\end{align}\\] So this set is indeed a subset of \\(\\mathbb P_3\\).
\nSince we have (ii) and (iv) be our choices, the answer is A.
\n\nProblem 6 Solution
\n\\[\n\\begin{vmatrix}4-\\lambda &2\\\\3 &5-\\lambda\\end{vmatrix}=\\lambda^2-9\\lambda+20-6=(\\lambda-2)(\\lambda-7)\n\\]
\nSo there are two eigenvalues 2 and 7. Since both are positive, the origin is a repeller. The answer is B.
\n\nProblem 7 Solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{1+i}\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t(\\cos t+i\\sin t)\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t\\begin{bmatrix}\\cos t+2\\sin t+i(\\sin t-2\\cos t)\\\\3\\cos t-4\\sin t+i(3\\sin t+4\\cos t)\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}\\cos t+2\\sin t\\\\3\\cos t-4\\sin t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\sin t-2\\cos t\\\\3\\sin t+4\\cos t\\end{bmatrix}\\]
\nThe answer is A.
\n\n
Problem 8 Solution
\n(1) Since \\(p(t)=at^2+bt+c\\), its derivative is \\(p'(t)=2at+b\\). So we can have \\[\nT(at^2+bt+c)=\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}\n\\]
\n(2) From the result of (1) above, we can directly write down that \\(c=1\\) and \\(b=2\\). Then because \\(2a+b=4\\), \\(a=2\\). So \\(p(t)=t^2+2t+1\\).
\n(3) Write down this transformation as the parametric vector form like below \\[\n\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}=\na\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix}+\nb\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix}+\nc\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\] So a basis for the range of \\(T\\) is \\[\n\\begin{Bmatrix}\n\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix},\n\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix},\n\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\end{Bmatrix}\n\\]
\n\n
Problem 9 Solution
\n(1) First find all the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}2-\\lambda &0 &0\\\\1 &5-\\lambda &1\\\\-1 &-3 &1-\\lambda\\end{vmatrix}&=(2-\\lambda)\\begin{vmatrix}5-\\lambda &1\\\\-3 &1\\lambda\\end{vmatrix}\\\\\n&=(2-\\lambda)(\\lambda^2-6\\lambda+5+3)\\\\\n&=(2-\\lambda)(\\lambda-2)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 2 with multiplicity and 4.
\nNow find out the eigenvector(s) for each eigenvalue
\nFor \\(\\lambda_1=\\lambda_2=2\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\-1 &-3 &-1\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\0 &0 &0\\end{bmatrix}\n\\] Convert this result to a parametric vector form with two free variables \\(x_2\\) and \\(x_3\\) \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\n\\begin{bmatrix}-3x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=\nx_2\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\n\\] So the basis for the eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
For \\(\\lambda_3=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}-2 &0 &0\\\\1 &1 &1\\\\-1 &-3 &-3\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &-2 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\n\\] This ends up with \\(x_1=0\\) and \\(x_2=-x_3\\). So the eigenvector is \\(\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\) or \\(\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\). The basis for the corresponding eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\) or \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\end{Bmatrix}\\).
(2) From the answers of (1), we can directly write down \\(P\\) and \\(D\\) as \\[\nP=\\begin{bmatrix}-3 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix},\\;\nD=\\begin{bmatrix}2 &0 &0\\\\0 &2 &0\\\\0 &0 &4\\end{bmatrix}\n\\]
\n\n
Problem 10 Solution
\n(1) First find the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}9-\\lambda &5\\\\-6 &-2-\\lambda\\end{vmatrix}\n&=\\lambda^2-7\\lambda-18-(-5)\\cdot 6\\\\\n&=\\lambda^2-7\\lambda+12\\\\\n&=(\\lambda-3)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 3 and 4.
\nFor \\(\\lambda_1=3\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}6 &5\\\\-6 &5\\end{bmatrix}\\sim\n\\begin{bmatrix}6 &5\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-5\\\\6\\end{bmatrix}\\).
Likewise, for \\(\\lambda_2=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}5 &5\\\\-6 &-6\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\).
(2) With the eigenvalues and corresponding eigenvectors known, we can apply them to the general solution formula \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So the answer is \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}-5\\\\6\\end{bmatrix}e^{3t}+c_2\\begin{bmatrix}-1\\\\1\\end{bmatrix}e^{4t}\n\\]
\n(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n-5c_1-c_2&=1\\\\\n6c_1+c_2&=0\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=-6\\). So \\(x(1)+y(1)=-5e^{3}+6e^4+6e^3-6e^4=e^3\\).
\n\nThis is the 3rd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Fall 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.
\nThere is hardly any theory which is more elementary [than linear algebra], in spite of the fact that generations of professors and textbook writers have obscured its simplicity by preposterous calculations with matrices.
— Jean Dieudonné (1906~1992, French mathematician, notable for research in abstract algebra, algebraic geometry, and functional analysis.)
Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm I covers the topics in Sections 1.1 – 3.3 of the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nHere are a few extra reference links for Purdue MA 26500:
\nProblem 1 Solution
\nBecause \\(C=B^{-1}A\\), we can left-multiply both sides by \\(B\\) and obtain \\(BC=BB^{-1}A=A\\). So \\[\n\\begin{bmatrix}0 & 1\\\\1 & 5\\\\\\end{bmatrix}\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] Further, compute matrix multiplication at the left side \\[\n\\begin{bmatrix}c &d\\\\a+5c &b+5d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] From here we can directly get \\(c=d=1\\), then \\(a=-2\\) and \\(b=-3\\). This leads to \\(a+b+c+d=-3\\).
\nThe answer is A.
\n\nProblem 2 Solution
\nThe reduced row echelon form has the same number of pivots as the original matrix. And the rank of a matrix \\(A\\) is just the number of pivot columns in \\(A\\). From these, we can deduce statement (iii) is true.
\nPer the Rank Theorem (rank \\(A\\) + dim Nul \\(A\\) = \\(n\\)), since \\(\\mathrm{Rank}(A)=\\mathrm{Rank}(R)\\), we obtain \\(\\mathrm{Nul}(A)=\\mathrm{Nul}(R)\\). So statement (i) is true as well.
\nFor a square matrix \\(A\\), suppose that transforming \\(A\\) to a matrix in reduced row-echelon form using elementary row operations \\(E_kE_{k−1}⋯E_1A=R\\). Taking the determinants of both sides, we get \\(\\det E_kE_{k−1}⋯E_1A=\\det R\\). Now, using the fact that the determinant of a product of matrices is the same as the product of the determinants of the matrices, we get that \\[\\det A=\\frac{\\det R}{\\det E_1⋯\\det E_k}\\]
\nAccording to the description in the \"Proofs of Theorems 3 and 6\" part in Section 3.2 Properties of Determinants, it is proven that \\(\\det E\\) would be either 1, -1, or a scalar. Taking all these into consideration, if \\(\\det R\\) is zero, \\(\\det A\\) must be zero. Statement (v) is true.
\n📝Notes:The reduced row echelon form of a square matrix is either the identity matrix or contains a row of 0's. Hence, \\(\\det R\\) is either 1 or 0.
\nNow look back at statement (ii), the column space of the matrix \\(A\\) is not necessarily equal to the column space of \\(R\\), because the reduced row echelon form could contain a row of 0's. In such a case, the spans of these two column spaces are different.
\nFor the same reason, we can conclude that the statement (iv) is false. Referring to Theorem 4 in Section 1.4 The Matrix Operation \\(A\\pmb x=\\pmb b\\) (check the \"Common Errors and Warnings\" in the end), \"For each \\(\\pmb b\\) in \\(\\pmb R^m\\), the equation \\(A\\pmb x=\\pmb b\\) has a solution\" is true if and only if \\(A\\) has a pivot position in every row (not column).
\nThe answer is A.
\n\nProblem 3 Solution
\nFirst, we can do row reduction to obtain the row echelon form of the standard matrix \\[\\begin{align}\n&\\begin{bmatrix}1 &a &a+1\\\\2 &a+2 &a-1\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\\\\\n\\sim&\\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &a(a-2) &(a+1)(a-2)\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &0 &-4a-2\\\\\\end{bmatrix}\n\\end{align}\\]
\nIf \\(a=2\\), the 2nd column is a multiple of the 1st column, so the columns of \\(A\\) are not linearly independent, then the transformation would not be one-to-one (Check Theorem 12 of Section 1.9 The Matrix of a Linear Transformation).
\nMoreover, if \\(a=-\\frac{1}{2}\\), the entries of the last row are all 0s. In such case, matrix \\(A\\) has only two pivots and \\(A\\pmb x=\\pmb 0\\) has non-trivial solutions, \\(L\\) is not one-to-one (See Theorem 11 of Section 1.9 The Matrix of a Linear Transformation).
\nSo the answer is C.
\n\nProblem 4 Solution
\nStatement A is wrong as none of these 3 vectors is a linear combination of the other two. They form a linearly independent set.
\nStatement B is wrong as we need 4 linearly independent vectors to span \\(\\mathbb R^4\\).
\nStatements C and D are also wrong because B is wrong. Not all vectors in \\(\\mathbb R^4\\) can be generated with a linear combination of these 3 vectors, and \\(A\\pmb x=\\pmb b\\) might have no solution.
\nStatements E is correct. It has a unique but trivial solution. Quoted from the textbook Section 1.7 Linear Independence:
\n\n\nThe columns of a matrix \\(A\\) are linearly independent if and only if the equation \\(A\\pmb x=\\pmb 0\\) has only the trivial solution.
\n
So the answer is E.
\n\nProblem 5 Solution
\nFrom the given condition, we know that \\(A\\) is a \\(m\\times n\\) matrix. So statement A is wrong.
\nStatement B is not necessarily true since \\(\\pmb b\\) could be outside of the range but still in the \\(\\mathbb R^m\\) as the codomain of \\(T\\). Statement E is also not true for the same reason.
\nStatement D is wrong. Since \\(m\\) is the row number of the matrix \\(A\\), rank \\(A=m\\) just means the number of pivots is equal to the row number. To have the column linearly independent, we need the pivot number to be the same as the column number.
\nNow we have only statement C left. If \\(m<n\\), the column vector set is linearly dependent. But \\(T\\) is one-to-one if and only if the columns of \\(A\\) are linearly independent. So \\(m<n\\) cannot be true.
\nThe answer is C.
\n\nProblem 6 Solution
\nThis is to solve the following equation system: \\[\n\\begin{bmatrix}2 &3\\\\1 &-1\\\\5 &4\\\\\\end{bmatrix}\n\\begin{bmatrix}x_1\\\\x_2\\\\\\end{bmatrix}=\n\\begin{bmatrix}1\\\\3\\\\6\\\\\\end{bmatrix}\n\\] Let's do the row reduction with the augmented matrix \\[\n\\begin{bmatrix}2 &3 &1\\\\1 &-1 &3\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\2 &3 &1\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-5\\\\0 &9 &-9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &1 &-1\\\\0 &0 &0\\\\\\end{bmatrix}\n\\]
\nThis yields the unique solution \\(x_1=2\\) and \\(x_2=-1\\). So the answer is B.
\n\nProblem 7 Solution
\nFirst, we can exclude E as it has a zero vector, and a vector set including a zero vector is always linearly dependent.
\nC has its column 2 equal to 2 times column 1. It is not linearly independent.
\nA is also wrong. It is easy to see that column 3 is equal to 2 times column 1 minus column 2.
\nB has zeros in row 3 of all four vectors. So all the vectors have only 3 valid entries. But we have 4 vectors. Referring to Theorem 8 of Section 1.7 Linear Independence, this is equivalent to the case that 4 vectors are all in 3D space. So there must be one vector that is a linear combination of the other 3. B is not the right answer.
\nD can be converted to the vector set \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\1\\\\0\\end{bmatrix},\n\\begin{bmatrix}0\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\] This is a linear independent vector set since we cannot get any column by linearly combining the other two.
\nSo the answer is D.
\n\n
Problem 8 Solution
\nStart with the augmented matrix and do row reduction \\[\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\3 &2 &2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &-1 &2-3a &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &0 &a(a-3) &a\\\\\\end{bmatrix}\n\\]
Apparently if \\(a=0\\), the last row has all zero entries, the system has one free variable and there are an infinite number of solutions.
If \\(a=3\\), the last row indicates \\(0=3\\), the system is inconsistent and has no solution.
If \\(a\\) is neither 3 nor 0, the row echelon form shows three pivots, thus the system has a unique solution.
Problem 9 Solution
\nThe sequence of row reduction to get the reduced row echelon form is shown below \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\2 &0 &-3 &-4 &5\\\\5 &0 &-6 &-1 &14\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &-1 &0 &-1\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &-2 &4\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]
From the reduced row echelon form, we can see that there are two pivots and three free variables \\(x_2\\), \\(x_4\\), and \\(x_5\\). So the system \\(A\\pmb x=\\pmb 0\\) becomes \\[\\begin{align}\nx_1-2x_4+4x_5&=0\\\\\nx_3+x_5&=0\n\\end{align}\\]
Now write the solution in parametric vector form. The general solution is \\(x_1=2x_4-4x_5\\), \\(x_3=-x_5\\). This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}2x_4-4x_5\\\\x_2\\\\-x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_2\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_4\\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\\begin{Bmatrix}\n \\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\end{Bmatrix}\\]
\n\n
Problem 10 Solution
\nFor computing the determinant of matrix \\(A\\) with the 1st column cofactor expansion, note that the only nonzero entry in column 1 is \\(a_{1,4}=2\\), so we have \\[\\begin{align}\n\\det A&=(-1)^{1+4}\\cdot 2\\cdot\\begin{vmatrix}1 &2 &3\\\\0 &\\color{fuchsia}3 &0\\\\1 &1 &1\\end{vmatrix}\\\\\n &=(-2)\\cdot 3\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=(-6)\\cdot(-2)=12\n\\end{align}\\]
From the adjugate of \\(A\\), we deduce the formula
\\[\\begin{align}\nb_{3,2}&=\\frac{C_{2,3}}{\\det A}=\\frac{1}{12}\\cdot(-1)^{2+3}\\begin{vmatrix}0 &1 &3\\\\0 &1 &1\\\\\\color{fuchsia}2 &0 &1\\end{vmatrix}\\\\\n&=\\frac{-1}{12}\\cdot(-1)^{3+1}\\cdot 2\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=\\frac{1}{3}\n\\end{align}\\]
\n\nHere is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \n
---|---|
1 | \nMatrix Multiplications, Inverse Matrix | \n
2 | \nColumn Space, Rank, Nul Space, Determinant, Pivot, Linear System Consistency | \n
3 | \nLinear Transformation, One-to-One Mapping | \n
4 | \nLinear Dependency, Vector Set Span \\(\\mathbb R^n\\), Unique Solution | \n
5 | \nLinear Transformation, One-to-One Mapping, Rank, Column Linear Independency, Vector Set Span \\(\\mathbb R^n\\) | \n
6 | \nBasis of Span \\({v_1, v_2}\\) | \n
7 | \nLinear Independency Vector Set | \n
8 | \nRow Echelon Form, Augmented Matrix, Linear System Solution Set and Consistency | \n
9 | \nReduced Row Echelon Form, Basis for the Null Space | \n
10 | \nDeterminant, Cofactor Expansion, Inverse Matrix, The Adjugate of Matrix | \n
As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.
\nHere are a few warnings collected from the textbook. It is highly recommended that students preparing for the MA 265 Midterm I exam review these carefully to identify common errors and know how to prevent them in the test.
\nThis is the 2nd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Spring 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.
\nMatrices act. They don't just sit there.
— Gilbert Strang (American mathematician known for his contributions to finite element theory, the calculus of variations, wavelet analysis and linear algebra.)
Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm I covers the topics of Sections 1.1 – 3.3 in the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nHere are a few extra reference links for Purdue MA 26500:
\nProblem 1 Solution
\nReferring to Section 3.2 Property of Determinants, we can do row and column operations to efficiently find the determinant of the given matrix.
\n\\[\\begin{align}\n\\begin{vmatrix}a &b &3c\\\\g &h &3i\\\\d+2a &e+2b &3f+6c\\\\\\end{vmatrix}&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d+2a &e+2b &3f+6c\\\\g &h &3i\\\\\\end{vmatrix}\\\\\n&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d &e &3f\\\\g &h &3i\\\\\\end{vmatrix}=\n(-1)\\cdot3\\begin{vmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\\\\\end{vmatrix}\\\\\n&=-3\\cdot 2=-6\n\\end{align}\\]
\nThe exact sequence of the operations are
\nSo the answer is B.
\n\nProblem 2 Solution
\nThis problem tests the students' knowledge of rank and dimension. Referring to Section 2.9 Dimension and Rank, we know the following important points:
\n\n\n\n
\n- Since the pivot columns of \\(A\\) form a basis for Col \\(A\\), the rank of \\(A\\) is just the number of pivot columns in \\(A\\).
\n- If a matrix \\(A\\) has \\(n\\) columns, then rank \\(A\\) + dim Nul \\(A\\) = \\(n\\).
\n
To find out the number of pivot columns in \\(A\\), we can do elementary row operations to obtain the Row Echelon Form of matrix \\(A\\).
\n\\[\\begin{align}\n&\\begin{bmatrix}1 &2 &2 &5 &0\\\\-2 &0 &-2 &2 &-4\\\\3 &4 &-1 &9 &2\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &4 &-4 &12 &-4\\\\0 &-2 &2 &-6 &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &1 &-1 &3 &-1\\\\0 &1 &-1 &3 &-1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &2 &2 &5 &0\\\\0 &\\color{fuchsia}{1} &-1 &3 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]
\nNow it is clear that this matrix has two pivot columns, thus rank \\(A\\) is 2, and dim Nul \\(A\\) is \\(5-2=3\\).
\nSince \\(5a-3b=5\\times 2-3\\times 3=1\\), the answer is A.
\n\nProblem 3 Solution
\nFor such linear transformation \\(T:\\mathbb R^3\\to\\mathbb R^3\\), onto means for each \\(\\pmb b\\) in the codomain \\(\\mathbb R^{3}\\), there exists at least one solution of \\(T(\\pmb x)=\\pmb b\\).
\nLet's do row reduction first to see
\n\\[\\begin{align}\n&\\begin{bmatrix}1 &t &2\\\\3 &3 &t-5\\\\2 &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\3 &3 &t-5\\\\1 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\1 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &0 &2-\\frac{(t-5)t}{3}\\\\\\end{bmatrix}\n\\end{align}\\]
\nNow inspect the entry of row 3 and column 3, it can be factorized as \\(\\frac{(6-t)(1+t)}{3}\\). If \\(t\\) is 6 or -1, this entry becomes 0. In such cases, for a nonzero \\(b_{3}\\) of \\(\\pmb b\\) in \\(\\mathbb R^{3}\\), there would be no solution at all.
\nSo to make this linear transformation onto \\(\\mathbb R^{3}\\), \\(t\\) cannot be 6 or -1. The answer is E.
\n\nProblem 4 Solution
\nLet's inspect the statements one by one.
\nFor (i), from Section 1.7 Linear Independence, because \\(A\\pmb x=\\pmb 0\\) has only a trivial solution, the columns of the matrix \\(A\\) are linearly independent. So there should be at most one solution for these column vectors to combine and obtain, this statement is true.
\nStatement (ii) is also true. If \\(m<n\\), according to Theorem 8 of Section 1.7, the set of column vectors is linearly dependent, etc a \\(2\\times 3\\) matrix (see Example 5 of Section 1.7). Then \\(A\\pmb x=\\pmb 0\\) has a nontrivial solution. Now referring to Theorem 11 of Section 1.9, this linear transformation of matrix \\(A\\) is NOT one-to-one.
\nThinking of the case \\(3\\times 2\\) for the linear transformation \\(T: \\mathbb R^2\\to\\mathbb R^3\\), we can get one-to-one mapping. But for \\(T: \\mathbb R^3\\to\\mathbb R^2\\), there could be more than 1 point in 3D space mapping to a 2D point. It is not one-to-one.
\nFor (iii), certainly this is not true. A simple example can be a \\(3\\times 2\\) matrix like below \\[\\begin{bmatrix}1 &0\\\\1 &1\\\\0 &1\\\\\\end{bmatrix}\\] The two columns above are NOT linearly dependent.
\nStatement (iv) is true as this is the exact case described by Theorem 4 (c) and (d) in Section 1.4.
\nThe answer is D.
\n\nProblem 5 Solution
\nFrom the given conditions, we know that the columns of \\(A\\) form a linearly dependent set. Equivalently this means \\(A\\) is not invertible and \\(A\\pmb x=\\pmb 0\\) has two nontrivial solutions \\[\\begin{align}\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}5\\\\1\\\\-6\\\\-2\\\\0\\end{bmatrix}=\\pmb 0\\\\\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}0\\\\2\\\\-7\\\\1\\\\3\\end{bmatrix}=\\pmb 0\\\\\n\\end{align}\\] So Statement E is false. Moveover, a noninvertible \\(A\\) has \\(\\det A = 0\\). The statement A is false too.
\nThe two nontrivial solutions for \\(A\\pmb x=\\pmb 0\\) are \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\). As they are also linear independent as one is not a multiple of the other, they should be in the basis for Nul \\(A\\). But we are not sure if there are also other vectors in the basis. We can only deduce that dim Nul \\(A\\) is at least 2. From this, we decide that statement B is false.
\nAgain because rank \\(A\\) + dim Nul \\(A\\) = \\(5\\), and dim Nul \\(A\\) is greater than or equal to 2, rank \\(A\\) must be less than or equal to 3. Statement C is true.
\nStatement D is not true either, since \\([1\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\) is not a linear combination of \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\).
\nSo the answer is C.
\n\nProblem 6 Solution
\nDenote the adjugate of \\(A\\) as \\(B=\\{b_{ij}\\}\\), then \\(b_{ij}=C_{ji}\\), where \\(C_{ji}\\) is the cofactor of \\(A\\). Compute two non-corner entries of \\(B\\) below \\[\\begin{align}\nb_{12}&=C_{21}=(-1)^{2+1}\\begin{vmatrix}0 &-1\\\\1 &-1\\end{vmatrix}=-1\\\\\nb_{21}&=C_{12}=(-1)^{1+2}\\begin{vmatrix}-5 &-1\\\\3 &-1\\end{vmatrix}=-8\n\\end{align}\\]
\nSo the answer is C.
\n\nProblem 7 Solution
\nWe need a set of 4 linearly independent vectors to span \\(\\mathbb R^4\\).
\nAnswer A contains the zero vector, thus the set is not linearly independent.
\nAnswer E contains only 3 vectors, not enough as the basis of \\(\\mathbb R^4\\).
\nAnswer D column 3 is 2 times column 2, and column 5 is equal to column 2 and column 4. So it has only 3 linearly independent vectors. Still not enough
\nAnswer C is also not correct. If we scale 1/3 to column 1, and then add it with columns 2 and 3 altogether, it results in column 4. So only 3 linearly independent vectors.
\nSo the answer is B. Indeed B has 4 linearly independent vectors.
\n\n
Problem 8 Solution
\nThis problem is very similar to Problem 8 of Fall 2022 Midterm I. The solution follows the same steps.
\nReferring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\] We can use this property to find \\(A\\).
\nFirst, denote \\(\\pmb u=\\begin{bmatrix}1\\\\1\\end{bmatrix}\\) and \\(\\pmb v=\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\). It is trivial to see that \\[\\begin{align}\n \\pmb{u}&=1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=\\pmb{e}_1+\\pmb{e}_2\\\\\n \\pmb{v}&=-1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=-\\pmb{e}_1+\\pmb{e}_2\\\\\n \\end{align}\\] This leads to \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}-\\frac{1}{2}\\pmb{v}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}+\\frac{1}{2}\\pmb{v}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=\\frac{1}{2}T(\\pmb{u})-\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=\\frac{1}{2}T(\\pmb{u})+\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}1\\\\1\\end{bmatrix}\n \\end{align}\\]
We know that the standard matrix is \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] as we have \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\) now, the standard matrix \\(A\\) is \\(\\begin{bmatrix}2 &1\\\\3 &1\\end{bmatrix}\\). It is a \\(2\\times 2\\) matrix. The inverse formula is (see Theorem 4 in Section 2.2 The Inverse of A Matrix) \\[\\begin{align}\n A&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\\\\n A^{-1}&=\\frac{1}{ad-bc}\\begin{bmatrix}d &-b\\\\-c &a\\end{bmatrix}\\\\\n \\end{align}\\] This yields \\(A^{-1}=\\begin{bmatrix}-1 &1\\\\3 &-2\\end{bmatrix}\\).
This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\(\\begin{bmatrix}2 &1 &7\\\\3 &1 &9\\end{bmatrix}\\). After row reduction, it becomes \\(\\begin{bmatrix}0 &1 &3\\\\1 &0 &2\\end{bmatrix}\\). This has unique solution \\(\\pmb x=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\).
📝Notes:The students should remeber the inverse formula of \\(2\\times 2\\) matrix!
\n\n
Problem 9 Solution
\nThis problem is also very similar to Problem 9 of Fall 2022 Midterm I. The solution follows the same steps.
\nThe augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &0 &-1 &1\\\\1 &1 &h-1 &3\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &0 &a^2-2h-3 &h-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a^2-2h-3\\).
When \\(h=3\\), the last row entries become all zeros. This system has an infinite number of solutions.
If \\(h=-1\\), last row becomes \\([0\\,0\\,0\\,-4]\\). Now the system is inconsistent and has no solution.
If \\(h\\) is not 3 or -1, last row becomes \\([0\\,0\\,h+1\\,1]\\). We get \\(z=\\frac{1}{h+1}\\). The system has a unique solution.
Problem 10 Solution
\nThis problem is also very similar to Problem 10 of Fall 2022 Midterm I. The solution follows the same steps.
\n\\[\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\1 &0 &5 &13 &20\\\\2 &0 &4 &12 &22\\\\3 &0 &2 &0 &21\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &3 &9 &9\\\\1 &0 &2 &6 &11\\\\0 &0 &-4 &-12 &-12\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &2 &0\\\\0 &0 &1 &3 &3\\end{bmatrix}\n\\] \\[\n\\sim\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &0 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5\\\\0 &0 &\\color{fuchsia}{1} &0 &3\\\\0 &0 &0 &\\color{fuchsia}{1} &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]
\nReferring to Theorem 12 Section 2.8 Matrix Algebra and the Warning message below that (quoted below)
\n\n\nWarning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). Thecolumns of an echelon form \\(B\\) are often not in the column space of \\(A\\).
\n
So the pivot columns of the original matrix \\(A\\) form a basis for the column space of \\(A\\). The basis is the set of columns 1, 3, and 4. \\[\n \\begin{Bmatrix}\\begin{bmatrix}1\\\\1\\\\2\\\\3\\end{bmatrix},\n \\begin{bmatrix}2\\\\5\\\\4\\\\2\\end{bmatrix},\n \\begin{bmatrix}4\\\\13\\\\12\\\\0\\end{bmatrix}\\end{Bmatrix}\n \\]
Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".
\nNow write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5 &0\\\\0 &0 &\\color{fuchsia}{1} &0 &3 &0\\\\0 &0 &0 &\\color{fuchsia}{1} &0 &0\\\\0 &0 &0 &0 &0 &0\\end{bmatrix}\\]
\nThe general solution is \\(x_1=-5x_5\\), \\(x_3=-3x_5\\), \\(x_4=0\\), with \\(x_2\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-5x_5\\\\x_2\\\\-3x_5\\\\0\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]
Here is the table listing the key knowledge points for each problem in this exam:
\nProblem # | \nPoints of Knowledge | \n
---|---|
1 | \nDeterminant and its Properties | \n
2 | \nRank and Dimension of the Null Space of a Matrix, Pivot Columns, Row Reduction Operation | \n
3 | \nLinear Transformation, Onto \\(\\mathbb R^m\\), Linear System Consistency | \n
4 | \nHomogeneous Linear Systems, One-to-One Mapping Linear Transformation, the Column Space of the Matrix | \n
5 | \nLinear Dependency, Invertible Matrix, Determinant, Rank and Dimension of the Null Space of Matrix | \n
6 | \nThe Adjugate of Matrix, The (\\(i,j\\))-cofactor of Matrix | \n
7 | \nLinear Independency, Vector Set Spanning Space \\(\\mathbb R^n\\) | \n
8 | \nLinear Transformation Properties, Standard Matrix for a Linear Transformation | \n
9 | \nRow Echelon Form, Linear System Solution Set and Consistency | \n
10 | \nReduced Row Echelon Form, Basis for the Column Vector Space and the Null Space | \n
As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/","content":"Here comes the solution and analysis for Purdue MA 26500 Spring 2023 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.
\nPurdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.
\n\nMA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.
\nBased on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,
\nProblem 1 Solution
\nA For \\(5\\times 7\\) matrix, if \\(rank(A)=5\\), the dimension of the null space is \\(7-5=2\\). So this is wrong.
\nB The matrix has 7 columns, but there are only 5 pivot columns, so the columns of \\(A\\) are NOT linearly independent. It is wrong.
\nC \\(A^T\\) is a \\(7\\times 5\\) matrix, and the rank of \\(A^T\\) is no more than 5. This statement is wrong.
\nD Because there are 5 pivots, each row has one pivot. Thus the rows of \\(A\\) are linearly independent. This statement is TRUE.
\nE From statement D, it can be deduced that the dimension of the row space is 5, not 2.
\nThe answer is D.
\n\nProblem 2 Solution
\nThe vector in this subspace \\(H\\) can be represented as \\[\na\\begin{bmatrix}1\\\\1\\\\0\\\\0\\end{bmatrix}+\nb\\begin{bmatrix}-2\\\\-1\\\\1\\\\0\\end{bmatrix}+\nc\\begin{bmatrix}9\\\\6\\\\-3\\\\0\\end{bmatrix}+\nd\\begin{bmatrix}5\\\\5\\\\1\\\\5\\end{bmatrix}+\ne\\begin{bmatrix}4\\\\-3\\\\-9\\\\-10\\end{bmatrix}\n\\]
\nHere the transformation matrix \\(A\\) has 5 columns and each has 4 entries. Hence these column vectors are not linearly independent.
\n\n\nNote that row operations do not affect the dependence relations between the column vectors. This makes it possible to use row reduction to find a basis for the column space.
\n
\\[\n\\begin{align}\n&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\1 &-1 &6 &5 &-3\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &0 &0 &1 &-2\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &-2 &9 &5 &4\\\\0 &\\color{fuchsia}1 &-3 &0 &-7\\\\0 &0 &0 &\\color{fuchsia}1 &-2\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\end{align}\n\\]
\nThe dimension of \\(H\\) is the number of linearly independent columns of the matrix, which is the number of pivots in \\(A\\)'s row echelon form. So the dimension is 3.
\nThe answer is C.
\n\nProblem 3 Solution
\nFirst, find the eigenvalues for the matrix \\[\n\\begin{align}\n\\det A-\\lambda I &=\\begin{vmatrix}2-\\lambda &2\\\\3 &1-\\lambda\\end{vmatrix}=(\\lambda^2-3\\lambda+2)-6\\\\&=\\lambda^2-3\\lambda-4=(\\lambda+1)(\\lambda-4)=0\n\\end{align}\n\\] The above gives two real eigenvalues \\(-1\\) and \\(4\\). Since they have opposite signs, the origin is a saddle point.
\nThe answer is A.
\n\nProblem 4 Solution
\n(i) is NOT true. Referring to Theorem 4 of Section 5.2 \"The Characteristic Equation\",
\n\n\nIf \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).
\n
But the reverse statement is NOT true. They are matrices that are not similar even though they have the same eigenvalues.
\n(ii) is NOT true either. Referring to Theorem 6 of Section 5.3 \"Diagonalization\",
\n\n\nAn \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n
The book mentions that the above theorem provides a sufficient condition for a matrix to be diagonalizable. So the reverse statement is NOT true. There are examples that a diagonalizable matrix has eigenvalues with multiplicity 2 or more.
\n(iii) Since the identity matrix is symmetric, and \\(\\det A=\\det A^T\\) for \\(n\\times n\\) matrix, we can write \\(\\det (A-\\lambda I) = \\det (A-\\lambda I)^T = \\det(A^T-\\lambda I)\\). So matrix \\(A\\) and its transpose have the same eigenvalues. This statement is TRUE.
\n(iv) This is definitely TRUE as we can find eigenvectors that are linearly independent and span \\(\\mathbb R^n\\).
\n(v) If matrix \\(A\\) has zero eigenvalue, \\(\\det A-0I=\\det A=0\\), it is not invertible. This statement is TRUE.
\nIn summary, statements (iii), (iv), and (v) are TRUE. The answer is E.
\n\nProblem 5 Solution
\nA This vector set does not include zero vector (\\(x = y = 0\\)). So it is not a subspace of \\(V\\).
\nB For eigenvalue 3, we can find out the eigenvector from \\(\\begin{bmatrix}0 &0\\\\2 &0\\end{bmatrix}\\pmb v=\\pmb 0\\), it is \\(\\begin{bmatrix}0\\\\\\ast\\end{bmatrix}\\). All vectors in this set satisfy three subspace properties. So this one is good.
\nC This cannot be the right choice. Since the 3rd entry is always 1, the vector set cannot be closed under vector addition and multiplication by scalars. Also, it does not include zero vector either.
\nD For \\(p(x)=a_0+a_1x+a_2x^2\\) and \\(p(1)p(2)=0\\), this gives \\[(a_0+a_1+a_2)(a_0+2a_1+4a_2)=0\\] To verify if this is closed under vector addition. Define \\(q(x)=b_0+b_1x+b_2x^2\\) that has \\(q(1)q(2)=0\\), this gives \\[(b_0+b_1+b_2)(b_0+2b_1+4b_2)=0\\] Now let \\(r(x)=p(x)+q(x)=c_0+c_1x+c_2x^2\\), where \\(c_i=a_i+b_i\\) for \\(i=0,1,2\\). Is it true that \\[(c_0+c_1+c_2)(c_0+2c_1+4c_2)=0\\] No, it is not necessarily the case. This one is not the right choice either.
\nE Invertible matrix indicates that its determinant is not 0. The all-zero matrix is certainly not invertible, so it is not in the specified set. Moreover, two invertible matrices can add to a non-invertible matrix, such as the following example \\[\n\\begin{bmatrix}2 &1\\\\1 &2\\end{bmatrix}+\\begin{bmatrix}-2 &1\\\\-1 &-2\\end{bmatrix}=\\begin{bmatrix}0 &2\\\\0 &0\\end{bmatrix}\n\\] This set is NOT a subspace of \\(V\\).
\nThe answer is B.
\n\nProblem 6 Solution
\nRecall from the Problem 4 solution that a matrix with \\(n\\) distinct eigenvalues is diagonalizable.
\n(i) The following calculation shows this matrix has two eigenvalues 4 and 1. So it is diagonalizable. \\[\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}=(\\lambda^2-5\\lambda+6)-2=(\\lambda-1)(\\lambda-4)=0\\]
\n(ii) It is easy to see that there is one eigenvalue \\(-3\\) with multiplicity 2. However, we can only get one eigenvector \\(\\begin{bmatrix}1\\\\0\\end{bmatrix}\\) for such eigenvalue. So it is NOT diagonalizable.
\n(iii) To find out the eigenvalues for this \\(3\\times 3\\) matrix, do the calculation as below \\[\n\\begin{vmatrix}2-\\lambda &3 &5\\\\0 &2-\\lambda &1\\\\0 &1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)(\\lambda-3)(\\lambda-1)\n\\] So we get 3 eigenvalues 2, 3, and 1. This matrix is diagonalizable.
\n(iv) This is an upper triangular matrix, so the diagonal entries (5, 4, 2) are all eigenvalues. As this matrix has three distinct eigenvalues, it is diagonalizable.
\nSince only (ii) is not diagonalizable, the answer is E.
\n\nProblem 7 Solution
\nThis problem involves complex eigenvalues.
\nStep 1: Find the eigenvalue of the given matrix \\[\n\\begin{vmatrix}1-\\lambda &-1\\\\1 &1-\\lambda\\end{vmatrix}=\\lambda^2-2\\lambda+2=0\n\\] Solve this with the quadratic formula \\[\n\\lambda=\\frac {-b\\pm {\\sqrt {b^{2}-4ac}}}{2a}=\\frac {-(-2)\\pm {\\sqrt {(-2)^2-4\\times 1\\times 2}}}{2\\times 1}=1\\pm i\n\\]
\nStep 2: Find the corresponding eigenvector for \\(\\lambda=1+i\\) \\[\n\\begin{bmatrix}-i &-1\\\\1 &-i\\end{bmatrix}\\sim\\begin{bmatrix}0 &0\\\\1 &-i\\end{bmatrix}\n\\] This gives \\(x_1=ix_2\\), so the eigervector can be \\(\\begin{bmatrix}i\\\\1\\end{bmatrix}\\).
\nStep 3: Generate the real solution
\nFrom Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)
\nNow use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\pmb{v}_1 e^{\\lambda_1 t}=e^t(\\cos t+i\\sin t)\\begin{bmatrix}i\\\\1\\end{bmatrix}\\\\\n=e^t\\begin{bmatrix}-\\sin t+i\\cos t\\\\\\cos t+i\\sin t\\end{bmatrix}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}-\\sin t\\\\\\cos t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\cos t\\\\\\sin t\\end{bmatrix}\\]
\nAt first glance, none on the list matches our answer above. However, let's inspect this carefully. We can exclude C and D first since they both have \\(e^{-t}\\) that is not in our answer. Next, it is impossible to be E because it has no minus sign.
\nNow between A and B, which one is most likely to be the right one? We see that B has \\(-\\cos t\\) on top of \\(\\sin t\\). That could not match our answer no matter what \\(c_2\\) is. If we switch \\(c_1\\) and \\(c_2\\) of A and inverse the sign of the 2nd vector, A would become the same as our answer. Since \\(c_1\\) and \\(c_2\\) are just scalars, this deduction is reasonable.
\nSo the answer is A.
\n\n
Problem 8 Solution
\n(1) Directly apply \\(p(t)=t^2-1\\) to the mapping function \\[T(t^2-1)=0^2-1+(1^2-1)t+(2^2-1)t^2=-1+3t^2\\]
\n(2) Denote \\(p(t)=a_0+a_1t+a_2t^2\\), \\(T(p(t))=b_0+b_1t+b_2t^2\\), then \\[\nT(a_0+a_1t+a_2t^2)=a_0+(a_0+a_1+a_2)t+(a_0+2a_1+4a_2)t^2\n\\] So \\[\n\\begin{align}\na_0 &&=b_0\\\\\na_0 &+ a_1 + a_2 &=b_1\\\\\na_0 &+ 2a_1 + 4a_2 &=b_2\n\\end{align}\n\\] This gives the \\([T]_B=\\begin{bmatrix}1 &0 &0\\\\1 &1 &1\\\\1 &2 &4\\end{bmatrix}\\).
\nAlternatively, we can form the same matrix with the transformation of each base vector: \\[\\begin{align}\nT(1)&=1+t+t^2 => \\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}\\\\\nT(t)&=0+t+2t^2 => \\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\\\\nT(t^2)&=0+t+4t^2 => \\begin{bmatrix}0\\\\1\\\\4\\end{bmatrix}\\\n\\end{align}\\]
\n\n
Problem 9 Solution
\n(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}1-\\lambda &2 &-1\\\\0 &3-\\lambda &-1\\\\0 &-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)\\begin{vmatrix}3-\\lambda &-1\\\\-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)(\\lambda-4)(\\lambda-1)\n\\] So there are \\(\\lambda_1=\\lambda_2=1\\), and \\(\\lambda_3=4\\).
\nNext is to find the eigenvectors for each eigenvalue
\nFor \\(\\lambda_1=\\lambda_2=1\\), apply row reduction to the agumented matrix of the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) \\[\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &2 &-1 &0\\\\0 &-2 &1 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &0 &0 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With two free variables \\(x_1\\) and \\(x_2\\), we get \\(x_3=2x_2\\). So the parametric vector form can be written as \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\nx_1\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}+x_2\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\n\\] So the eigenvectors are \\(\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\\) and \\(\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\).
For \\(\\lambda_3=4\\), follow the same process \\[\n\\begin{bmatrix}-3 &2 &-1 &0\\\\0 &-1 &-1 &0\\\\0 &-2 &-2 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}3 &-2 &1 &0\\\\0 &1 &1 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With one free variable \\(x_3\\), we get \\(x_1=x_2=-x_3\\). So the eigenvector can be written as \\(\\begin{bmatrix}1\\\\1\\\\-1\\end{bmatrix}\\) (or \\(\\begin{bmatrix}-1\\\\-1\\\\1\\end{bmatrix}\\)).
(2) We can directly construct \\(P\\) from the vectors in last step, and construct \\(D\\) from the corresponding eigenvalues. Here are the answers: \\[\nP=\\begin{bmatrix}\\color{fuchsia}1 &\\color{fuchsia}0 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}1 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}2 &\\color{blue}{-1}\\end{bmatrix},\\;\nD=\\begin{bmatrix}\\color{fuchsia}1 &0 &0\\\\0 &\\color{fuchsia}1 &0\\\\0 &0 &\\color{blue}4\\end{bmatrix}\n\\]
\n\n
Problem 10 Solution
\n(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\)
\n\\[\\begin{vmatrix}-4-\\lambda &-5\\\\2 &3-\\lambda\\end{vmatrix}=(\\lambda^2+\\lambda-12)+10=(\\lambda+2)(\\lambda-1)=0\\] So there are two eigervalues \\(-2\\) and 1. Next is to find the eigenvectors for each eigenvalue.
For \\(\\lambda=-2\\), the matrix becomes \\[\\begin{bmatrix}-2 &-5\\\\2 &5\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\2 &5\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}5\\\\-2\\end{bmatrix}\\).
\nFor \\(\\lambda=1\\), the matrix becomes \\[\\begin{bmatrix}-5 &-5\\\\2 &2\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\1 &1\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).
\n(2) The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}5\\\\-2\\end{bmatrix}e^{-2t}+c_2\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^t\n\\]
\n(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n5c_1+c_2&=-3\\\\\n-2c_1-c_2&=0\n\\end{align}\\] This gives \\(c_1=-1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=-5e^{-2}+2e^1+2e^{-2}-2e^{-1}=-3e^{-2}\\).
\n\nHere are the key knowledge points covered by this exam:
\nNetwork Attached Storage (NAS) provides data access to a heterogeneous group of clients over computer networks. As hard drive prices continue to drop, NAS devices have made their way into the homes of the masses. Leading brands in the SMB and home NAS market, such as Synology, have their products range in price from as low as ﹩300 to ﹩700 for the high models. But if you are a Raspberry Pi player, you can build a very nice home NAS and streaming service for only about half the cost of the lowest price.
\nKnowledge obtained on the papers always feels shallow, must know this thing to practice.
— LU You (Chinese historian and poet of the Southern Song Dynasty)
This blog records the whole process of building a Raspberry Pi NAS and home media server, including project planning, system implementation, and performance review. It also covers some important experiences and lessons that could hopefully benefit anyone interested in this DIY project.
\nRaspberry Pi 4B features an upgraded 1.8GHz Broadcom BCM2711(quad-core Cortex-A72)processor and onboard RAM up to 8GB. It includes two new USB 3.0 ports and a full-speed Gigabit Ethernet interface. The power supply is also updated to a USB-C connector. All these greatly improve system throughput and overall comprehensive performance, and we can use them to create a full-featured home NAS.
For NAS system software, OpenMediaVault (OMV) is a complete NAS solution based on Debian Linux. It is a Linux rewrite of the well-known free and open-source NAS server system FreeNAS (based on FreeBSD). The salient features of OMV are
\nWhile primarily designed for home environments or small home offices, OMV's use is not limited to those scenarios. The system is built on a modular design. It can be easily extended with available plugins right after the installation of the base system. OMV is the NAS server system software we are looking for.
\nThe NAS system with media playback services provides an excellent audio/video-on-demand experience in a home network environment. Plex Media Server software integrates Internet media services (YouTube, Vimeo, TED, etc.) and local multimedia libraries to provide streaming media playback on users' various devices. The features of Plex for managing local libraries are
\nThe Plex Media Server software itself is free and supports a wide range of operating systems, making it ideal for integration with home NAS.
\nThese cover all the software needed for our NAS project, but they are not enough for a complete NAS system. We also need a preferred case, otherwise, the Raspberry Pi NAS will only run bare metal. Although there are many cases available in the market for Raspberry Pi 4B, as a NAS system we need a case kit that can accommodate at least 1-2 internal SSD/HDD and must also have a good heat dissipation design.
\nAfter some review and comparison, we chose Geekworm's NASPi Raspberry Pi 4B NAS storage kit. NASPi is a NUC (Next Unit of Computing) style NAS storage kit designed for the latest Raspberry Pi 4B. It consists of three components:
\nAll these components are packed into a case made of aluminum alloy with an anodized surface.
\nThereon our NAS project can be planned with the following subsystems:
\nIt is important to note that NAS servers are generally headless systems without a keyboard, mouse, or monitor. This poses some challenges for the installation, configuration, and tuning of hardware and software systems. In practice, as described in the next section, we run an SSH terminal connection to complete the basic project implementation process.
\nThe execution of this project was divided into four stages, which are described in detail as follows.
\nIn the first stage, we need to prepare the Raspberry Pi OS and do some basic unit tests. This is important, if we delay the OS test until the entire NSAPi kit is assembled, it will be troublesome to find problems with the Raspberry Pi then.
\nFirst, insert the microSD card into the USB adapter and connect it to the macOS computer, then go to the Raspberry Pi website and download the Raspberry Pi Imager software to run. From the application screen, click CHOOSE OS > Raspberry Pi OS (other) > Raspberry Pi OS Lite (32-bit) step by step. This selects the lightweight Raspberry Pi OS that does not require a desktop environment, and then click CHOOSE STORAGE to pick the microSD card.
\nNext is a trick - hit the ctrl-shift-x
key combination and the following advanced options dialog box will pop up Here is exactly the option we need to enable SSH on boot up - Enable SSH. It also allows the user to pre-set a password for the default username
pi
(default is raspberry). Once set up, click SAVE to return to the main page and then click WRITE to start formatting the microSD card and writing OS to it. When finished, remove the microSD card and insert it into the Raspberry Pi, connect the Ethernet cable then power it up.
At this point we encountered a problem: since the installed system does not have a desktop environment, it cannot connect to the keyboard, mouse, and monitor, so how do we find its IP address? There are two ways:
\nThe log of the Nmap tool run can be seen below. Notice that a new IP address 192.168.2.4 is showing up in the scan report. Rerunning Nmap against this address alone, we saw that TCP port 22 was open. We could roughly determine that this might be our newly online Raspberry Pi:
\n❯ nmap -sn 192.168.2.0/24 |
Next, try SSH connection
\n❯ ssh pi@192.168.2.4 |
Once confirmed, we executed the following commands in the Raspberry Pi to update and upgrade the system:
\npi@raspberrypi:~ $ sudo apt update && sudo apt upgrade |
This stage concluded with the stability test of the Raspberry Pi 4B system Ethernet connection. The test was executed on a macOS computer using the simple ping command, setting the -i 0.05
option to specify 20 packets per second and the -t 3600
option for one hour run
❯ sudo ping -i 0.05 192.168.2.4 -t 3600 |
There should be no more than 1% packet loss or timeout on a subnet with no wireless connectivity, otherwise, it should be checked for troubleshooting. As a matter of fact, in our test, it was happening that nearly 10% of ping packets got lost and the SSH connection dropped intermittently. Searching the Internet, we found that there have been quite a few reports of similar issues with the Raspberry Pi 4B Ethernet connection. The analysis and suggestions given by people on the relevant forums focus on the following
\nPractically, we tried all of the above with little success. Later, we found that the home router connected to the Raspberry Pi 4B was a Belkin N750 DB made in 2011. Although it provides Wi-Fi dual-band 802.11n and 4 Gigabit Ethernet ports, the manufacturing date is too long ago, which makes people doubt its interoperability. Also points 2 and 3 of the above report are essentially interoperability issues. Thinking of these, we immediately ordered the TP-Link TL-SG105 5-port Gigabit Ethernet switch. After receiving it, we extended the Gigabit Ethernet port of N750 with TL-SG105, connected Raspberry Pi 4B to TL-SG105, and retested it. Sure enough, this time the ping packet loss rate was less than 0.1% and the SSH connection became solid.
\nThe conclusion is that the Raspberry Pi 4B Gigabit Ethernet interface may have compatibility issues with some older devices, which can be solved by inserting a desktop switch with good interoperability between the two.
\nIn the second stage, we assembled the NSAPi storage kit, intending to finish all hardware installation and complete the standalone NAS body.
\nThe NSAPi supports either an internal SSD or HDD. The project picked a Samsung 870 EVO 500GB internal SSD, here we ought to first make sure the SSD works properly on its own, otherwise, we would have to disassemble the NASPi to replace it. The SSD can be hooked up to Windows for file systems and basic read/write operation checks. In the case of a newly purchased SSD, the following steps can be done on Windows to quickly format it:
\n⚠️Note: Here the chosen file system is NTFS. OMV supports NTFS mounting and reads/writes.
\nBefore the actual hardware assembly, a special software provided by Geekworm - PWM fan control script - must be installed. PWM fan speed adjustment to temperature change is a major feature that lets NASPi stand out from other hardware solutions. So this step is critical.
\nReferring to Geekworm's X-C1 software wiki page, the installation command sequence on the SSH session connected to the Raspberry Pi 4B system is as follows
\nsudo apt-get install -y git pigpio |
If you can't do git clone
directly on Raspberry Pi 4B, you can first download the X-C1 software on the SSH client, then transfer it to Raspberry Pi 4B using scp. After that, continue to execute the subsequent commands
❯ scp -r x-c1 pi@192.168.2.4:/home/pi/ |
How does X-C1 software control PWM fan?
\nThe core of X-C1 software is a Python script named fan.py, which is presented below
\n#!/usr/bin/python |
Its logic is quite simple. With the pigpio module imported, it first initializes a PWM control object and then starts a while loop with a 1-second sleep cycle inside. The CPU temperature is read at each cycle, and the duty cycle of PWM is set according to the temperature level to control the fan speed. The duty cycle is 0 when it is lower than 30℃, and the fan stops; when it is higher than 75℃, the duty cycle is 100, and the fan spins at full speed. Users can modify the temperature threshold and duty cycle parameters in the program to customize the PWM fan control.
\n\nIn addition, the following pi-temp.sh script, which reads out the GPU and CPU temperatures, is also useful
\npi@raspberrypi:~ $ cat ./pi-temp.sh |
Below is a snapshot of the Geekworm NASPi parts out of the box (except for the Raspberry Pi 4B on the far right of the second row and the screwdriver in the lower right corner)
\n The three key components in the second row, from left to right, are
The assembly process was done step-by-step mainly by referring to NASPi installation video on Youtube, and the steps are generalized as follows.
\nNow the installation of the internal accessories has been completed, we have a view of this
\nAt this point, we added the USB-C power and pressed the front button to start the system, we could see the PWM fan started to spin. It was also observed that the fan spin rate was not constant, which demonstrated that the temperature controller PWM fan was working properly.
\nThe front button switch with embedded blue LED decides the whole system's on/off state and can be tested below
\nRunning the off
command on the SSH connection can also trigger a safe shutdown. Be cautious that we should not use the Linux shutdown
command, as that would not power down the X-C1 board.
After the button switch test, we now unplugged the USB 3.0 connector and inserted the entire module into the case. Next was to add the back panel and tighten the screws, then re-insert the USB 3.0 connector. This completed the whole NASPi storage kit assembly process. Below are the front and rear views of the final system provided by Geekworm (all interfaces and vents are marked).
\nThe third stage is for installing and configuring the key software package of the NAS system - PMV. The goal is to bring up the basic network file access service. Before restarting the NAS, we plugged a Seagate 2TB external HDD into the remaining USB 3.0 port. After booting, connected SSH to NASPi from macOS and performed the following process.
\nInstalling OMV is as simple as running the following command line directly from a terminal with an SSH connection.
\nwget -O - https://raw.githubusercontent.com/OpenMediaVault-Plugin-Developers/installScript/master/install | sudo bash |
Due to the large size of the entire OMV package, this installation process can take a long time. After the installation, the IP address of the system may change and you will need to reconnect to SSH at this time.
\n(Reading database ... 51781 files and directories currently installed.) |
After reconnecting, you can use dpkg
to view the OMV packages. As you can see, the latest version of OMV installed here is 6.0.5.
pi@raspberrypi:~ $ dpkg -l | grep openme |
At this point OMV's workbench is live. Launching a browser on a macOS computer and typing in the IP address will open the beautiful login screen (click on the 🌍 icon in the upper right corner to select the user interface language): After logging in with the default username and password shown above, you will see the Workbench screen. The first thing you should do at this point is to click the ⚙️ icon in the top right corner to bring up the settings menu and click \"Change Password\". You can also change the language here
Clicking on \"Dashboard\" in the settings menu allows you to select the relevant components to be enabled. The menu on the left side provides task navigation for administrators and can be hidden when not needed. The complete OMV administration manual can be found in the online documentation
Next is the key process for configuring the NAS, which consists of the following 5 steps.
\nScan for mounted disk drives
\nClick Storage > Disks from the sidebar menu to enter the hard drive management page. If there is an external USB storage device just plugged in, you can click 🔍 here to scan it out. The scan results for this system are as follows. The internal Samsung 500GB SSD and external Seagate 2TB HDD are detected, and the 32GB microSD that contains the entire software system is listed at the top:
On the SSH terminal, we could see the information for the same set of mounted drivers
\npi@raspberrypi:~ $ df -h | grep disk
/dev/sdb2 466G 13G 454G 3% /srv/dev-disk-by-uuid-D0604B68604B547E
/dev/sda1 1.9T 131G 1.7T 7% /srv/dev-disk-by-uuid-DEB2474FB2472B7B
Mount disk drive file systems
\nClick Storage > File Systems from the sidebar menu to enter the file system management page. If the storage device does not have a file system yet, click ⨁ to Create or Mount the file system. OMV can create/mount ext4, ext3, JFS, and xfs file systems, but only mounts are supported for the NTFS file system. The following figure shows that OMV correctly mounts NTFS file systems for SSDs and HDDs:
Set Shared Folders
\nFrom the sidebar menu, click Storage > File Systems to access the shared folder management page. Here, click ⨁ to create a shared folder. When creating it, specify the name, corresponding file system, and relative path, and you can also add comments. Select the created folder and click the pencil icon again to edit the related information. This system sets the relative paths of shared folders Zixi-Primary and Zixi-Secondary for SSD and HDD respectively Notice the orange alert at the top of the figure above, which alerts the administrator that the configurations have changed and must click on the ✔️ icon to take effect.
Add shared folder access users
\nClick User Management > Users from the sidebar menu to enter the user management page. The system's default user pi has root privileges and cannot be used for file-sharing access due to security concerns. So you need to add a new user separately. On this page, click ⨁ to Create or Import user, only user name and password are required when creating a new user, others are optional. Once created, select this user and click the third folder+key icon (prompting \"Shared folder privileges\") to enter the following privileges settings page As shown in the figure, for this new user zixi, the administrator can set the read and write access permissions for each shared folder.
Start file share services
\nIf you expand the \"Services\" item in the navigation menu, you can see that OMV manages five services: FTP, NFS, Rsync, SMB/CIFS, and SSH. SSH is enabled at the beginning of the system OS image preparation. NFS and SMB/CIFS are the most common network file-sharing protocols, and both are supported by macOS. Take SMB/CIFS as an example here. Click Services > SMB/CIFS from the sidebar menu to enter the management page. The page contains two buttons: Settings and Shares. Click \"Settings\" first to activate the SMB/CIFS service and configure the workgroup name on the new page, other options can be left as default. After saving, it returns to the SMB/CIFS administration page. Then enter \"Shares\", click ⨁ to Create shared folders Zixi-Primary and Zixi-Secondary on the new page then save. After that, click the ✔️ icon in the orange warning bar to make all configuration updates take effect, and you will end up with the following result
Now our Raspberry Pi NAS system is ready for file sharing and the SMB/CIFS service is started. After checking the relevant components to turn on, our dashboard live monitoring looks like this
Once the server side is ready, we need to add the network share folder on the client side as follows.
\nOnce the client side is set up, users can perform various operations on the network share folder as if it were a local directory, such as previewing, creating new, opening or copying files, creating new subdirectories, or deleting existing subdirectories.
\nThe last stage is to install and configure the Plex Media Server, and then start a network streaming service.
\nThe process of installing Plex Media Server requires HTTPS transport support, so we must first install the https-transport package. SSH to our Raspberry Pi NAS and execute the install command
\nsudo apt-get install apt-transport-https |
Next add the Plex repository to the system, which requires downloading the Plex sign key first. Here are the related commands and run logs
\npi@raspberrypi:~ $ curl https://downloads.plex.tv/plex-keys/PlexSign.key | sudo apt-key add - |
Use the same apt-key
command to check the newly added Plex sign key
pi@raspberrypi:~ $ apt-key list |
You can see that Plex uses 4096-bit RSA keys. For the warning message \"apt-key is deprecated...\" in the above log, you can ignore it for now. Go to read some discussion on the askubuntu forum if you are interested.
\nThe next step is to add the Plex repository to the system repository list, and then update the packages echo deb https://downloads.plex.tv/repo/deb public main | sudo tee /etc/apt/sources.list.d/plexmediaserver.list
sudo apt-get update
pi@raspberrypi:~ $ sudo apt install plexmediaserver |
The log shows a question is asked about the Plex media server list (plexmediaserver.list), just choose the default N. When we see \"Installation successful\", we know that the installation was successful. At this point, the Plex streaming service is up and running. Invoking the Nmap scan again from the macOS side, we find that TCP port 32400 for Plex service is open.
\n❯ nmap -p1-65535 192.168.2.4 | grep open |
The configuration of the Plex Media Server has been done on the web GUI. Launch a browser on the macOS computer and type in the URL http://<IP-address>:32400/web, now we can see the following page if no surprise We can sign in with a Google, Facebook, or Apple account, or we can enter an email to create a new account. Follow the instructions on the page step by step, no need for any payment, soon we reach the Server Setup page. Here we can configure the server name and add libraries. Normally we don't need to access our home media server from outside, so remember to uncheck the \"Allow me to access my media outside my home\" box in this step. To add a library, first select the type of library (movies, TV episodes, music, photos, etc.), then click the \"BROWSE FOR MEDIA FOLDER\" button to browse and select the corresponding folder. Once the library is added, the included media files will immediately appear in the local service directory, as shown in the screenshot below
Here we have a local server named ZIXI-RPI-NAS for our Raspberry Pi NAS, the movie directory in the library shows The Matrix trilogy and is playing the first one The Matrix. Move your mouse over the server name and ➕ icon will appear to the right, click on it to continue adding new media libraries.
Once the Plex Media Server is configured, we can open a browser from any device on our home network to do streaming on-demand, without the need to download additional applications. The whole experience is just like our own proprietary home Netflix service. This is awesome!
\nBy connecting a macOS laptop to one of the remaining ports of the TL-SG105, we could perform some simple same-subnet tests to evaluate the performance of this NAS system fully.
\nReferring to Geekworm NASPi Stress Test Wiki page, we executed the following command over SSH connection, which cloned the test script from GitHub and ran the stress test:
\ngit clone https://github.com/geekworm-com/rpi-cpu-stress |
Simultaneously we established a second SSH session and ran htop
to monitor system status. The screenshot below was taken while close to the 5-minute mark (left is the htop real-time display, and right is the stress test output) Dividing the
temp
value on the right side by 1000 gave the CPU temperature. All 4 CPU cores reached 100% full load during the test, while the maximum temperature did not exceed 70°C. At this moment, there was no obvious heat sensation when touching the case. Typing ctrl-c
to stop the stress test, and then executing the temperature measurement script again
pi@raspberrypi:~ $ ./pi-temp.sh |
The system temperature returned to a low range value. This test result assures the system meets the design goal.
\nThe file transfer speed can be roughly measured with the secure remote copy tool SCP. First, create a 1GB size file by running the mkfile
command on the macOS client, then copy it to the user directory of the remote NAS system with the scp
command
❯ mkfile 1G test-nas.dmg |
After the copy was done, it would print the time spent and the deduced speed. Running the command with the source and the destination reversed would give us the speed of receiving a file from the NAS system.
\n❯ scp pi@192.168.2.4:/home/pi/test-nas.dmg test-nas-rx.dmg |
Repeated 3 times and got the results listed below
\nTransfor Type | \nServer Operation | \nTime (s) | \nSpeed (MB/s) | \n
---|---|---|---|
Send | \nWrite | \n53 | \n19.2 | \n
Send | \nWrite | \n45 | \n22.5 | \n
Send | \nWrite | \n50 | \n20.4 | \n
Receive | \nRead | \n15 | \n65.7 | \n
Receive | \nRead | \n16 | \n60.3 | \n
Receive | \nRead | \n15 | \n66.3 | \n
As can be seen, the speed of remote write is around 20MB/s, while the speed of remote file read can reach over 60MB/s. Considering that scp-related encryption and decryption are implemented in software on general-purpose Raspberry Pi systems, this result should be considered passable.
\nThe real test of the NAS's performance is the network drive read/write speed test. For this, we downloaded the AmorphousDiskMark app from Apple's App Store. This is an easy and efficient drive speed test that measures the read/write performance of a storage device in terms of MB/s and IOPS (input/output operations per second). It has four types of tests:
\nThe above queue depths are the default values, but other values are also available. In addition, users can also modify the test file size and duration.
\nRun the application on the macOS client and select the remote SMB folders Zixi-Primary (Samsung SSD) and Zixi-Secondary (Seagate HDD) respectively at the top, then click the All
button in the upper left corner to start the NAS drive speed test process. A side-by-side comparison of the two test results is shown below
This gives a few observations:
\nThese are not surprising and are consistent with the test results on macOS laptops with direct external SSDs and HDDs, only with the lower numbers. With this NAS system, both the SSD and HDD are connected via the USB 3.0 interface. USB 3.0 supports transfer speeds of up to 5Gbit/s, so the performance bottleneck of the system is the network interface bandwidth and processor power.
\nThat being said, for both SSDs and HDDs, the transfer speeds have been more than 900Mbit/s at 1MB sequential read and queue depth 8, close to the upper bandwidth limit of the Gigabit Ethernet interface. This read speed can support a single 1080p60 video stream at a frame rate of 60fps or 2 parallel 1080i50 video streams at a frame rate of 25fps, which is sufficient for home streaming services. In another media service test, the NAS system performs satisfactorily with three computers playing HD video on demand and one phone playing MP3 music without any lag.
\nThis completes our Raspberry Pi home NAS project. Now we can move our NAS to a more permanent location to provide network file and streaming services for the whole family.
\nEconomically, our home NAS has the cost summarized in the table below (excluding SSD/HDD)
\nDevices | \nFunctions | \nCost($) | \n
---|---|---|
Raspberry Pi 4B 2/4/8GB RAM | \nPrimary hardware system | \n45/55/75 | \n
Samsung 32GB EVO+ Class-10 Micro SDHC | \nOS storage | \n10 | \n
Geekworm NASPi Raspberry Pi 4B NAS Storage Kit | \nCase, extending board and PWM fan | \n60 | \n
Geekworm 20W 5V 4A USB-C Power Adaptor | \nPower supply | \n15 | \n
TP-Link TL-SG105 5-Port Gigabit Ethernet Switch | \nDesktop switch | \n15 | \n
Even with the choice of 8GB RAM Raspberry Pi 4B, the whole cost is only $175, a little more than half of the price of the low-end brand NAS sold in the market. Unless there are a lot of client devices that need streaming services, the memory consumption is usually under 2GB, so the 2GB Raspberry Pi 4B should be able to work in most home scenarios. That cuts the cost down to $145, less than half the MSRP.
\nOn the other hand, this DIY project was a very good exercise of hands-on practice, helping us gain valuable intuitive experience in building network connections, configuring system hardware and software, and tuning and testing application layer services. To sum up, the home NAS system built with Raspberry Pi 4B and OMV, combined with a Plex media server, provides a cost-effective solution for file backup and streaming media services in the home network.
\nAppendix: List of related devices and Amazon links
\n\n\n","categories":["DIY Projects"],"tags":["Raspberry Pi","NAS"]},{"title":"RSA: Attack and Defense (II)","url":"/en/2023/11/17/RSA-attack-defense-2/","content":"Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\nCanaKit Raspberry Pi 4B 8GB RAM + 128GB MicroSD Extrem Kit https://amzn.to/3DUeDfm
\n
\nSamsung 32GB EVO+ Class 10 Micro SDHC with Adapter https://amzn.to/3FLkTb7
\nGeekworm NASPi 2.5\" SATA HDD/SSD Raspberry Pi 4B NAS Storage Kit https://amzn.to/3m5djAi
\nGeekworm Raspberry Pi 4 20W 5V 4A USB-C Power Adaptor https://amzn.to/3m1EXOf
\nTP-Link TL-SG105 5-Port Gigabit Ethernet Switch https://amzn.to/3pRkBsi
\nSamsung 870 EVO 500GB 2.5\" SATA III Internal SSD https://amzn.to/3DPKnCl
\nSeagate Portable 2TB USB 3.0 External HDD https://amzn.to/3EYegl4
\nSynology 2-Bay 2GB NAS DiskStation DS220+ https://amzn.to/3Jp5qjd
\nSynology 5-Bay 8GB NAS DiskStation DS1520+ https://amzn.to/3qniQDm
This article first supplements two specific integer factorization methods - Fermat's factorization method and Pollard's rho algorithm, explaining the essence of their algorithms and applicable scenarios, and provides a Python reference implementation. Next, it analyzes in detail a classic low private exponent attack - Wiener's attack, elaborating on the mathematical basis, the attack principle, and the attack procedure, with a complete Python program. The article also cites the latest research paper proposing a new upper bound for the private exponent when Wiener's attack is successful and verifies the correctness of this limit with a test case.
\nThe enemy knows the system being used.
— Claude Shannon (American mathematician, electrical engineer, computer scientist, and cryptographer known as the \"father of information theory\".)
Previous article: RSA: Attack and Defense (I)
\nEven if the RSA modulus \\(N\\) is a very big number (with sufficient bits), problems can still arise if the gap between the prime factors \\(p\\) and \\(q\\) is too small or too large. In such cases, there are specific factorization algorithms that can effectively retrieve p and q from the public modulus N.
\nWhen the prime factors \\(p\\) and \\(q\\) are very close, Fermat's factorization method can factorize the modulus N in a very short time. Fermat's factorization method is named after the French mathematician Pierre de Fermat. Its base point is that every odd integer can be represented as the difference between two squares, i.e. \\[N=a^2-b^2\\] Applying algebraic factorization on the right side yields \\((a+b)(a-b)\\). If neither factor is one, it is a nontrivial factor of \\(N\\). For the RSA modulus \\(N\\), assuming \\(p>q\\), correspondingly \\(p=a+b\\) and \\(q=a-b\\). In turn, it can be deduced that \\[N=\\left({\\frac {p+q}{2}}\\right)^{2}-\\left({\\frac {p-q}{2}}\\right)^{2}\\] The idea of Fermat's factorization method is to start from \\(\\lceil{\\sqrt N}\\rceil\\) and try successive values of a, then verify if \\(a^{2}-N=b^{2}\\). If it is true, the two nontrivial factors \\(p\\) and \\(q\\) are found. The number of steps required by this method is approximately \\[{\\frac{p+q}{2}}-{\\sqrt N}=\\frac{({\\sqrt p}-{\\sqrt q})^{2}}{2}=\\frac{({\\sqrt N}-q)^{2}}{2q}\\] In general, Fermat's factorization method is not much better than trial division. In the worst case, it may be slower. However, when the difference between \\(p\\) and \\(q\\) is not large, and \\(q\\) is very close to \\(\\sqrt N\\), the number of steps becomes very small. In the extreme case, if the difference between \\(q\\) and \\(\\sqrt N\\) is less than \\({\\left(4N\\right)}^{\\frac 1 4}\\), this method only takes one step to finish.
\nBelow is a Python implementation of Fermat's factorization method, and an example of applying it to factorize the RSA modulus N:
\nimport gmpy2 |
The FermatFactor()
function defined at the beginning of the program implements the Fermat factorization method. It calls three library functions of gmpy2: isqrt()
to find the square root of an integer, square()
to execute the squaring operation, and is_square()
to verify if the input is a square number. Two large prime numbers \\(p\\) and \\(q\\) of 154 decimal digits each are defined later, and multiplying them gives \\(N\\). Then \\(N\\) is fed into the FermatFactor()
function and the program starts timing. When the function returns, it prints the elapsed time and confirms the factorization.
N = 55089599753625499150129246679078411260946554356961748980861372828434789664694269460953507615455541204658984798121874916511031276020889949113155608279765385693784204971246654484161179832345357692487854383961212865469152326807704510472371156179457167612793412416133943976901478047318514990960333355366785001217 |
As can be seen, in less than half a minute, this large number of 308 decimal digits (about 1024 bits) was successfully factorized! Going back and examining \\(p\\) and \\(q\\), one can see that the first 71 digits of these two large prime numbers of 154 decimal digits are exactly the same. This is exactly the scenario in which the Fermat factorization method exerts its power. If you simply modify the FermatFactor()
function to save the starting \\(a\\) value and compare it to the value at the end of the loop, you get a loop count of 60613989. With such a small number value, it's no wonder that the factorization is done so quickly.
Therefore, the choice of the large prime numbers \\(p\\) and \\(q\\) must not only be random but also be far enough apart. After obtaining two large prime numbers, the difference between them shall be checked. If it is too small, regeneration is required to prevent attackers from using Fermat's factorization method to crack it.
\nOn the opposite end, if the gap between the large prime factors \\(p\\) and \\(q\\) is too large, they may be cracked by Pollard's rho algorithm. This algorithm was invented by British mathematician John Pollard1 in 1975. It requires only a small amount of storage space, and its expected running time is proportional to the square root of the smallest prime factor of the composite number being factorized.
\nThe core idea of Pollard's rho algorithm is to use the collision pattern of traversal sequences to search for factors, and its stochastic and recursive nature allows it to factorize integers efficiently in relatively low complexity. First, for \\(N=pq\\), assume that \\(p\\) is the smaller nontrivial factor. The algorithm defines a polynomial modulo \\(N\\) \\[f(x)=(x^{2}+c){\\pmod N}\\] A pseudorandom sequence can be generated by making recursive calls with this polynomial, and the sequence generation formula is \\(x_{n+1}=f(x_n)\\). For example, given an initial value of \\(x_0=2\\) and a constant \\(c=1\\), it follows that \\[\\begin{align}\nx_1&=f(2)=5\\\\\nx_2&=f(x_1)=f(f(2))=26\\\\\nx_3&=f(x_2)=f(f(f(2)))=677\\\\\n\\end{align}\\] For two numbers \\(x_i\\) and \\(x_j\\) in the generated sequence, \\(|x_i-x_j|\\) must be a multiple of \\(p\\) if \\(x_i\\neq x_j\\) and \\(x_i\\equiv x_j{\\pmod p}\\). In this case, calculating \\(\\gcd(|x_i-x_j|,N)\\) results in \\(p\\). Based on the Birthday Paradox, in the worst case, it is expected that after generating about \\(\\sqrt p\\) numbers, there will be two numbers that are the same under the modulus \\(p\\), thus successfully factorizing \\(N\\). However, the time complexity of performing pairwise comparisons is still unsatisfactory. In addition, storing so many numbers is also troublesome when N is large.
\nHow to solve these problems? This is where the ingenuity of Pollard's rho algorithm lies. Pollard found that the sequence generated by this pseudorandom number generator has two properties:
\nInsightful of these two properties, Pollard utilizes Floyd's cycle-finding algorithm (also known as the tortoise and hare algorithm) to set up the fast and slow nodes \\(x_h\\) and \\(x_t\\). Starting from the same initial value \\(x_0\\), the slow node \\(x_t\\) moves to the next node in the sequence every step, while the fast node \\(x_h\\) moves forward by two nodes at a time, i.e. \\[\\begin{align}\nx_t&=f(x_t)\\\\\nx_h&=f(f(x_h))\\\\\n\\end{align}\\] After that, calculate \\(\\gcd(|x_h-x_t|,N)\\), and the result that is greater than 1 and less than \\(N\\) is \\(p\\), otherwise continue with the same steps. With this design, since each move is equivalent to checking a new node spacing, pairwise comparisons are unnecessary. If not found, eventually the fast and slow nodes will meet on the cycle, at which time the result of finding the greatest common divisor is \\(N\\). The algorithm's recommendation at this point is to exit and regenerate the pseudorandom number sequence with a different initial value or constant \\(c\\) and try again.
\nThis is the classic Pollard's rho algorithm. Its time complexity is \\(𝑂(\\sqrt p\\log N)\\) (\\(\\log\\) comes from the required \\(\\gcd\\) operations). For RSA modulus \\(N\\), obviously \\(p\\leq \\sqrt N\\), so the upper bound on the time complexity can be written as \\(𝑂(N^{\\frac 1 4}\\log N)\\). The time complexity expression for Pollard's rho algorithm indicates that the smaller the minimum prime factor of the composite number being factorized, the faster the factorization is expected to be. An excessively small \\(p\\) is extremely unsafe.
\nProgramming Pollard's rho algorithm is not difficult. The following Python code shows a function implementation of the algorithm, PollardRhoFactor()
, and some test cases
import gmpy2 |
The function PollardRhoFactor()
accepts three arguments: n
is the composite number to be factorized, seed
is the initial value of the pseudorandom sequence, and c
is the constant value in the generating polynomial. The function internally uses two while
to form a double loop: inside the outer loop defines the generating polynomial f
and the fast and slow nodes h
and t
, while the node moving steps and the greatest common divisor operation are implemented in the inner loop. The inner loop ends only if the greatest common divisor d
is not 1. At this point, if d
is not equal to n
, the function returns the non-trivial factor d
. Otherwise, d
equals n
, meaning the fast and slow nodes have met on the cycle. In this situation, the code in the outer loop resets seed
to the value of the fast node and increments c
, thus restarting a new round of search.
Running the above code on a MacBook Pro (2019), the output is as follows
\nN P Elapsed Time (s) |
This result proves the effectiveness of Pollard's rho algorithm. In particular, for the last test, the input to the function was the Fermat number \\(F_8\\) (defined as \\(F_{n}=2^{2^{n}}+1\\), where \\(n\\) is a non-negative integer). In 1980, Pollard and Australian mathematician Richard Brent 2 working together applied this algorithm to factorize \\(F_8\\) for the first time. The factorization took 2 hours on a UNIVAC 1100/42 computer. And now, on a commercial off-the-shelf laptop computer, Pollard's rho algorithm revealed the smaller prime factor 1238926361552897 of \\(F_8\\) in 64.4 seconds.
\nSubsequently, Pollard and Brent made further improvements to the algorithm. They observed that if \\(\\gcd(d, N)>1\\), for any positive integer \\(k\\), there is also \\(\\gcd(kd, N)>1\\). So multiplying \\(k\\) consecutive \\((|x_h-x_t| \\pmod N)\\) and taking the modulo \\(N\\) with the product, and then solving for the greatest common divisor with \\(N\\) should obtain the same result. This method replaces \\(k\\) times \\(\\gcd\\) with \\((k-1)\\) times multiplications modulo \\(N\\) and a single \\(\\gcd\\), thus achieving acceleration. The downside is that occasionally it may cause the algorithm to fail by introducing a repeated factor. When this happens, it then suffices to reset \\(k\\) to 1 and fall back to the regular Pollard's rho algorithm.
\nThe following Python function implements the improved Pollard's rho algorithm. It adds an extra for
loop to implement the multiplication of \\(k\\) consecutive differences modulo \\(N\\), with the resulting product stored in the variable mult
. mult
is fed to the greatest common divisor function with \\(N\\), and the result is assigned to d
for further check. If this fails, \\(k\\) is set to 1 in the outer loop.
def PollardRhoFactor2(n, seed, c, k): |
Using the same test case, called with \\(k\\) set to 100, the program runs as follows
\nN P Elapsed Time (s) |
It can be seen that for relatively small composite \\(N\\), the improvement is not significant. As \\(N\\) becomes larger, the speedup is noticeable. For the 78-bit decimal Fermat number \\(F_8\\), the improved Pollard's rho algorithm takes only 46.6 seconds, which is a speedup of more than 27% over the regular algorithm. The improved Pollard \\(\\rho\\) algorithm indeed brings significant speedup.
\nTo summarize the above analysis, implementation, and testing of Pollard's rho algorithm, it is necessary to set a numerical lower bound for the generated prime numbers \\(p\\) and \\(q\\) to be used by RSA. If either of them is too small, it must be regenerated or it may be cracked by an attacker applying Pollard's rho algorithm.
\nFor some particular application scenarios (e.g., smart cards and IoT), limited by the computational capability and low-power requirements of the device, a smaller value of private exponent \\(d\\) is favored for fast decryption or digital signing. However, a very low private exponent is very dangerous, and there are some clever attacks that can totally breach such an RSA cryptosystem.
\nIn 1990, Canadian cryptographer Michael J. Wiener conceived an attack scheme3 based on continued fraction approximation that can effectively recover the private exponent \\(d\\) from the RSA public key \\((N, e)\\) under certain conditions. Before explaining how this attack works, it is important to briefly introduce the concept and key properties of continued fraction.
\nThe continuous fraction itself is just a mathematical expression, but it introduces a new perspective on the study of real numbers. The following is a typical continued fraction \\[x = a_0 + \\cfrac{1}{a_1 + \\cfrac{1}{a_2 + \\cfrac{1}{\\ddots\\,}}}\\] where \\(a_{0}\\) is an integer and all other \\(a_{i}(i=1,\\ldots ,n)\\) are positive integers. One can abbreviate the continued fraction as \\(x=[a_0;a_1,a_2,\\ldots,a_n]\\). Continued fractions have the following properties:
\nEvery rational number can be expressed as a finite continued fraction, i.e., a finite number of \\(a_{i}\\). Every rational number has an essentially unique simple continued fraction representation with infinite terms. Here are two examples: \\[\\begin{align}\n\\frac {68} {75}&=0+\\cfrac {1} {1+\\cfrac {1} {\\small 9+\\cfrac {1} {\\scriptsize 1+\\cfrac {1} {2+\\cfrac {1} {2}}}}}=[0;1,9,1,2,2]\\\\\nπ&=[3;7,15,1,292,1,1,1,2,…]\n\\end{align}\\]
To calculate the continued fraction representation of a positive rational number \\(f\\), first subtract the integer part of \\(f\\), then find the reciprocal of the difference and repeat till the difference is zero. Let \\(a_i\\) be the integer quotient, \\(r_i\\) be the difference of the \\(i\\)th step, and \\(n\\) be the number of steps, then \\[\\begin{align}\na_0 &= \\lfloor f \\rfloor, &r_0 &= f - a_0\\\\\na_i&={\\large\\lfloor} \\frac 1 {r_{i-1}} {\\large\\rfloor}, &r_i &=\\frac 1 {r_{i-1}} - a_i \\quad (i = 1, 2, ..., n)\\\\\n\\end{align}\\] The corresponding Python function implementing the continued fraction expansion of rationals is as follows
\ndef cf_expansion(nm: int, dn:int) -> list:
""" Continued Fraction Expansion of Rationals
Parameters:
nm - nominator
dn - denomainator
Return:
List for the abbreviated notation of the continued fraction
"""
cf = []
a, r = nm // dn, nm % dn
cf.append(a)
while r != 0:
nm, dn = dn, r
a = nm // dn
r = nm % dn
cf.append(a)
return cf
For both rational and irrational numbers, the initial segments of their continued fraction representations produce increasingly accurate rational approximations. These rational numbers are called the convergents of the continued fraction. The even convergents continually increase, but are always less than the original number; while the odd ones continually decrease, but are always greater than the original number. Denote the numerator and denominator of the \\(i\\)-th convergent as \\(h_i\\) and \\(k_i\\) respectively, and define \\(h_{-1}=1,h_{-2}=0\\) and \\(k_{-1}=0,k_{-2}=1\\), then the recursive formula for calculating the convergents is \\[\\begin{align}\n\\frac {h_0} {k_0} &= [0] = \\frac 0 1 = 0<\\frac {68} {75}\\\\\n\\frac {h_1} {k_1} &= [0;1] = \\frac 1 1 = 1>\\frac {68} {75}\\\\\n\\frac {h_2} {k_2} &= [0;1,9] = \\frac 9 {10}<\\frac {68} {75}\\\\\n\\frac {h_3} {k_3} &= [0;1,9,1] = \\frac {10} {11}>\\frac {68} {75}\\\\\n\\frac {h_4} {k_4} &= [0;1,9,1,2] = \\frac {29} {32}<\\frac {68} {75}\\\\\n\\end{align}\\] It can be verified that these convergents satisfy the aforementioned property and are getting closer to the true value. The following Python function implements a convergent generator for a given concatenated fraction expansion, and it returns a tuple of objects consisting of the convergent's numerator and denominator.
\ndef cf_convergent(cf: list) -> (int, int):
""" Calculates the convergents of a continued fraction
Parameters:
cf - list for the continued fraction expansion
Return:
A generator object of the convergent tuple
(numerator, denominator)
"""
nm = [] # Numerator
dn = [] # Denominators
for i in range(len(cf)):
if i == 0:
ni, di = cf[i], 1
elif i == 1:
ni, di = cf[i]*cf[i-1] + 1, cf[i]
else: # i > 1
ni = cf[i]*nm[i-1] + nm[i-2]
di = cf[i]*dn[i-1] + dn[i-2]
nm.append(ni)
dn.append(di)
yield ni, di
Regarding the convergents of continued fractions, there is also an important Legendre4 theorem: Let \\(a∈ \\mathbb Z, b ∈ \\mathbb Z^+\\) such that \\[\\left\\lvert\\,f - \\frac a b\\right\\rvert< \\frac 1 {2b^2}\\] then \\(\\frac a b\\) is a convergent of the continued fraction of \\(f\\).
Now analyze how Wiener's attack works. From the relationship between RSA public and private exponent \\(ed\\equiv 1 {\\pmod {\\varphi(N)}}\\), it can be deduced that there exists an integer \\(k\\) such that \\[ed - k\\varphi(N) = 1\\] Dividing both sides by \\(d\\varphi(N)\\) gives \\[\\left\\lvert\\frac e {\\varphi(N)} - \\frac k d\\right\\rvert = \\frac 1 {d{\\varphi(N)}}\\] Careful observation of this formula reveals that because \\(\\varphi(N)\\) itself is very large, and \\(\\gcd(k,d)=1\\), \\(\\frac k d\\) is very close to \\(\\frac e {\\varphi(N)}\\). In addition, \\[\\varphi(N)=(p-1)(q-1)=N-(p+q)+1\\] Its difference from \\(N\\) is also relatively small. Therefore, \\(\\frac k d\\) and \\(\\frac e N\\) also do not differ by much. Since RSA's \\((N,e)\\) are public, Wiener boldly conceived - if \\(\\pmb{\\frac e N}\\) is expanded into a continued fraction, it is possible that \\(\\pmb{\\frac k d}\\) is one of its convergents!
\nSo how to verify if a certain convergent is indeed \\(\\frac k d\\)? With \\(k\\) and \\(d\\), \\(\\varphi (N)\\) can be calculated, thereby obtaining \\((p+q)\\). Since both \\((p+q)\\) and \\(pq\\) are known, constructing a simple quadratic equation5 can solve for \\(p\\) and \\(q\\). If their product equals \\(N\\), then \\(k\\) and \\(d\\) are correct and the attack succeeds.
\nWhat are the conditions for Wiener's attack to work? Referring to Legendre's theorem mentioned above, it can be deduced that if \\[\\left\\lvert\\frac e N - \\frac k d\\right\\rvert < \\frac 1 {2{d^2}}\\] then \\(\\frac k d\\) must be a convergent of \\(\\frac e N\\). This formula can also be used to derive an upper bound of the private exponent d for a feasible attack. Wiener's original paper states the upper bound as \\(N^{\\frac 1 4}\\), but without detailed analysis. In 1999, American cryptographer Dan Boneh6 provided the first rigorous proof of the upper bound, showing that under the constraints \\(q<p<2q\\) and \\(e<\\varphi(N)\\), Wiener's attack applies for \\(d<\\frac 1 3 N^{\\frac 1 4}\\). In a new paper published in 2019, several researchers at the University of Wollongong in Australia further expanded the upper bound under the same constraints to \\[d\\leq \\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4=\\frac 1 {2.06...}N^\\frac 1 4\\]
\nNote that for simplicity, the above analysis of Wiener's attack mechanism is based on the Euler phi function \\(\\varphi (N)\\). In reality, RSA key pairs are often generated using the Carmichael function \\(\\lambda(N)\\). The relationship between the two is: \\[\\varphi (N)=\\lambda(n)\\cdot\\gcd(p-1,q-1)\\] It can be proven that starting from \\(ed≡1{\\pmod{\\lambda(N)}}\\), the same conclusions can be reached. Interested readers may refer to Wiener's original paper for details.
\nWith an understanding of the mechanism of Wiener's attack, the attack workflow can be summarized as follows:
\nThe complete Python implementation is as follows:
\nimport gmpy2 |
The code above ends with two test cases. Referring to the program output below, the first test case gives a small RSA modulus \\(N\\) and a relatively large \\(e\\), which is precisely the scenario where Wiener's attack comes into play. The program calls the attack function wiener_attack() that quickly returns \\(d\\) as 7, then decrypts a ciphertext and recovers the original plaintext \"Wiener's attack success!\".
\nThe second test case sets a 2048-bit \\(N\\) and \\(e\\), and Wiener's attack also succeeds swiftly. The program also verifies that the cracked \\(d\\) (511 bits) is greater than the old bound old_b
(\\(N^{\\frac 1 4}\\)), but slightly less than the new bound new_b
(\\(\\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4\\)). This confirms the conclusion of the University of Wollongong researchers.
p = 105192975360365123391387526351896101933106732127903638948310435293844052701259 |
These two test cases prove the effectiveness and prerequisites of Wiener's attack. To prevent Wiener's attack, the RSA private exponent \\(d\\) must be greater than the upper bound. Choosing \\(d\\) no less than \\(N^{\\frac 1 2}\\) is a more prudent scheme. In practice, the optimized decryption using Fermat's theorem and Chinese remainder theorem is often used, so that even larger \\(d\\) can achieve fast decryption and digital signing.
\n\n\nTo be continued, stay tuned for the next article: RSA: Attack and Defense (III)
\n
John Pollard, a British mathematician, the recipient of 1999 RSA Award for Excellence in Mathematics for major contributions to algebraic cryptanalysis of integer factorization and discrete logarithm.↩︎
Richard Peirce Brent, an Australian mathematician and computer scientist, an emeritus professor at the Australian National University.↩︎
M. Wiener, “Cryptanalysis of short RSA secret exponents,” IEEE Trans. Inform. Theory, vol. 36, pp. 553–558, May 1990↩︎
Adrien-Marie Legendre (1752-1833), a French mathematician who made numerous contributions to mathematics.↩︎
Dan Boneh, an Israeli–American professor in applied cryptography and computer security at Stanford University, a member of the National Academy of Engineering.↩︎
RSA encryption algorithm is one of the core technologies of modern public-key cryptography and is widely used on the Internet. As a classical algorithm of public-key cryptography, the programming implementation of textbook RSA can help us quickly grasp its mathematical mechanism and design ideas, and accumulate important experience in the software implementation of cryptography. Here is a detailed example of textbook RSA implementation in Python 3.8 programming environment.
\nRandom numbers should not be generated with a method chosen at random.
— Donald Knuth(American computer scientist, mathematician, and professor emeritus at Stanford University, the 1974 recipient of the ACM Turing Award, often called the \"father of the analysis of algorithms\")
The security of the RSA encryption algorithm is built on the mathematical challenge of factoring the product of two large prime numbers. The first step in constructing the RSA encryption system is to generate two large prime numbers \\(p\\) and \\(q\\), and calculate the modulus \\(N=pq\\). \\(N\\) is the length of the RSA key, the larger the more secure. Nowadays, practical systems require the key length to be no less than 2048 bits, with corresponding \\(p\\) and \\(q\\) about 1024 bits each.
\nA general effectiveness method for generating such large random prime numbers is a probability-based randomization algorithm, which proceeds as follows:
\nIn this software implementation, the first step can generate odd numbers directly. Also for demonstration purposes, the second step uses the first 50 prime numbers greater than 2 for the basic primality test. The whole process is shown in the following flowchart.
\nFor the first step, Python function programming requires importing the library function randrange()
from the random
library. The function uses the input number of bits n in the exponents of 2, which specify the start and end values of randrange()
. It also sets the step size to 2 to ensure that only n-bit random odd values are returned.
from random import randrange |
The code for the second step is simple. It defines an array with elements of 50 prime numbers after 2, then uses a double loop in the function to implement the basic primality test. The inner for
loop runs the test with the elements of the prime array one by one. It aborts back to the outer loop immediately upon failure, from there it calls the function in the first step to generate the next candidate odd number and test again.
def get_lowlevel_prime(n): |
The Miller-Rabin primality test1 in the third step is a widely used method for testing prime numbers. It uses a probabilistic algorithm to determine whether a given number is a composite or possibly a prime number. Although also based on Fermat's little theorem, the Miller-Rabin primality test is much more efficient than the Fermat primality test. Before showing the Python implementation of the Miller-Rabin prime test, a brief description of how it works is given here.
\nBy Fermat's little theorem, for a prime \\(n\\), if the integer \\(a\\) is not a multiple of \\(n\\), then we have \\(a^{n-1}\\equiv 1\\pmod n\\). Therefore if \\(n>2\\), \\(n-1\\) is an even number and must be expressed in the form \\(2^{s}*d\\), where both \\(s\\) and \\(d\\) are positive integers and \\(d\\) is odd. This yields \\[a^{2^{s}*d}\\equiv 1\\pmod n\\] If we keep taking the square root of the left side of the above equation and then modulo it, we will always get \\(1\\) or \\(-1\\)2. If we get \\(1\\), it means that the following equation ② holds; if we never get \\(1\\), then equation ① holds: \\[a^{d}\\equiv 1{\\pmod {n}}{\\text{ ①}}\\] \\[a^{2^{r}d}\\equiv -1{\\pmod {n}}{\\text{ ②}}\\] where \\(r\\) is some integer that lies in the interval \\([0, s-1]\\). So, if \\(n\\) is a prime number greater than \\(2\\), there must be either ① or ② that holds. The conditional statement of this law is also true, i.e.** if we can find a \\(\\pmb{a}\\) such that for any \\(\\pmb{0\\leq r\\leq s-1}\\) the following two equations are satisfied: \\[\\pmb{a^{d}\\not \\equiv 1\\pmod n}\\] \\[\\pmb{a^{2^{r}d}\\not \\equiv -1\\pmod n}\\] Then \\(\\pmb{n}\\) must not be a prime number**. This is the mathematical concept of the Miller-Rabin primality test. For the number \\(n\\) to be tested, after calculating the values of \\(s\\) and \\(d\\), the base \\(a\\) is chosen randomly and the above two equations are tested iteratively. If neither holds, \\(n\\) is a composite number, otherwise, \\(n\\) may be a prime number. Repeating this process, the probability of \\(n\\) being a true prime gets larger and larger. Calculations show that after \\(k\\) rounds of testing, the maximum error rate of the Miller-Rabin primality test does not exceed \\(4^{-k}\\).
\nThe Miller-Rabin primality test function implemented in Python is as follows, with the variables n,s,d,k
in the code corresponding to the above description.
def miller_rabin_primality_check(n, k=20): |
Putting all of the above together, the whole process can be wrapped into the following function, where the input of the function is the number of bits and the output is a presumed random large prime number.
\ndef get_random_prime(num_bits): |
Greatest Common Divisor (GCD) gcd(a,b)
and Least Common Multiple lcm(a,b)
:
\nThe RSA encryption algorithm needs to calculate the Carmichael function \\(\\lambda(N)\\) of modulus \\(N\\), with the formula \\(\\lambda(pq)= \\operatorname{lcm}(p - 1, q - 1)\\), where the least common multiple function is used. The relationship between the least common multiple and the greatest common divisor is: \\[\\operatorname{lcm}(a,b)={\\frac{(a\\cdot b)}{\\gcd(a,b)}}\\] There is an efficient Euclidean algorithm for finding the greatest common divisor, which is based on the principle that the greatest common divisor of two integers is equal to the greatest common divisor of the smaller number and the remainder of the division of the two numbers. The specific implementation of Euclid's algorithm can be done iteratively or recursively. The iterative implementation of the maximum convention function is applied here, and the Python code for the two functions is as follows:
def gcd(a, b):
'''Computes the Great Common Divisor using the Euclid's algorithm'''
while b:
a, b = b, a % b
return a
def lcm(a, b):
"""Computes the Lowest Common Multiple using the GCD method."""
return a // gcd(a, b) * b
Extended Euclidean Algorithm exgcd(a,b)
and Modular Multiplicative Inverse invmod(e,m)
:
\nThe RSA key pair satisfies the equation \\((d⋅e)\\bmod \\lambda(N)=1\\), i.e., the two are mutually modular multiplicative inverses with respect to the modulus \\(\\lambda(N)\\). The extended Euclidean algorithm can be applied to solve the modular multiplicative inverse \\(d\\) of the public key exponent \\(e\\) quickly. The principle of the algorithm is that given integers \\(a,b\\), it is possible to find integers \\(x,y\\) (one of which is likely to be negative) while finding the greatest common divisor of \\(a,b\\) such that they satisfy Bézout's identity: \\[a⋅x+b⋅y=\\gcd(a, b)\\] substituted into the parameters \\(a=e\\) and \\(b=m=\\lambda(N)\\) of the RSA encryption algorithm, and since \\(e\\) and \\(\\lambda(N)\\) are coprime, we can get: \\[e⋅x+m⋅y=1\\] the solved \\(x\\) is the modulo multiplicative inverse \\(d\\) of \\(e\\). The Python implementations of these two functions are given below:
def exgcd(a, b):
"""Extended Euclidean Algorithm that can give back all gcd, s, t
such that they can make Bézout's identity: gcd(a,b) = a*s + b*t
Return: (gcd, s, t) as tuple"""
old_s, s = 1, 0
old_t, t = 0, 1
while b:
q = a // b
s, old_s = old_s - q * s, s
t, old_t = old_t - q * t, t
a, b = b, a % b
return a, old_s, old_t
def invmod(e, m):
"""Find out the modular multiplicative inverse x of the input integer
e with respect to the modulus m. Return the minimum positive x"""
g, x, y = exgcd(e, m)
assert g == 1
# Now we have e*x + m*y = g = 1, so e*x ≡ 1 (mod m).
# The modular multiplicative inverse of e is x.
if x < 0:
x += m
return x
Note: Textbook RSA has inherent security vulnerabilities. The reference implementation in the Python language given here is for learning and demonstration purposes only, by no means to be used in actual applications. Otherwise, it may cause serious information security incidents. Keep this in mind!
\nBased on the object-oriented programming idea, it can be designed to encapsulate the RSA keys and all corresponding operations into a Python class. The decryption and signature generation of the RSA class are each implemented in two ways, regular and fast. The fast method is based on the Chinese Remainder Theorem and Fermat's Little Theorem. The following describes the implementation details of the RSA class.
\nObject Initialization Method
\nInitialization method __init__()
has the user-defined paramaters with default values shown as below:
This method internally calls the get_random_prime()
function to generate two large random prime numbers \\(p\\) and \\(q\\) that are about half the bit-length of the key. It then calculates their Carmichael function and verifies that the result and \\(e\\) are coprime. If not, it repeats the process till found. Thereafter it computes the modulus \\(N\\) and uses the modular multiplicative inverse function invmod()
to determine the private exponent \\(d\\). If a fast decryption or signature generation function is required, three additional values are computed as follows: \\[\\begin{align}\nd_P&=d\\bmod (p-1)\\\\\nd_Q&=d\\bmod (q-1)\\\\\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\n\\end{align}\\]
RSA_DEFAULT_EXPONENT = 65537
RSA_DEFAULT_MODULUS_LEN = 2048
class RSA:
"""Implements the RSA public key encryption/decryption with default
exponent 65537 and default key size 2048"""
def __init__(self, key_length=RSA_DEFAULT_MODULUS_LEN,
exponent=RSA_DEFAULT_EXPONENT, fast_decrypt=False):
self.e = exponent
self.fast = fast_decrypt
t = 0
p = q = 2
while gcd(self.e, t) != 1:
p = get_random_prime(key_length // 2)
q = get_random_prime(key_length // 2)
t = lcm(p - 1, q - 1)
self.n = p * q
self.d = invmod(self.e, t)
if (fast_decrypt):
self.p, self.q = p, q
self.d_P = self.d % (p - 1)
self.d_Q = self.d % (q - 1)
self.q_Inv = invmod(q, p)
Encryption and Decryption Methods
\nRSA encryption and regular decryption formulas are \\[\\begin{align}\nc\\equiv m^e\\pmod N\\\\\nm\\equiv c^d\\pmod N\n\\end{align}\\] Python built-in pow()
function supports modular exponentiation. The above two can be achieved by simply doing the corresponding integer to byte sequence conversions and then calling pow() with the public or private key exponent:
def encrypt(self, binary_data: bytes):
int_data = uint_from_bytes(binary_data)
return pow(int_data, self.e, self.n)
\t
def decrypt(self, encrypted_int_data: int):
int_data = pow(encrypted_int_data, self.d, self.n)
return uint_to_bytes(int_data)
def decrypt_fast(self, encrypted_int_data: int):
# Use Chinese Remaider Theorem + Fermat's Little Theorem to
# do fast RSA description
assert self.fast == True
m1 = pow(encrypted_int_data, self.d_P, self.p)
m2 = pow(encrypted_int_data, self.d_Q, self.q)
t = m1 - m2
if t < 0:
t += self.p
h = (self.q_Inv * t) % self.p
m = (m2 + h * self.q) % self.n
return uint_to_bytes(m)
Signature Generation and Verification Methods
\nThe RSA digital signature generation and verification methods are very similar to encryption and regular decryption functions, except that the public and private exponents are used in reverse. The signature generation uses the private exponent, while the verification method uses the public key exponent. The implementation of fast signature generation is the same as the fast decryption steps, but the input and output data are converted and adjusted accordingly. The specific implementations are presented below:
def generate_signature(self, encoded_msg_digest: bytes):
"""Use RSA private key to generate Digital Signature for given
encoded message digest"""
int_data = uint_from_bytes(encoded_msg_digest)
return pow(int_data, self.d, self.n)
\t
def generate_signature_fast(self, encoded_msg_digest: bytes):
# Use Chinese Remaider Theorem + Fermat's Little Theorem to
# do fast RSA signature generation
assert self.fast == True
int_data = uint_from_bytes(encoded_msg_digest)
s1 = pow(int_data, self.d_P, self.p)
s2 = pow(int_data, self.d_Q, self.q)
t = s1 - s2
if t < 0:
t += self.p
h = (self.q_Inv * t) % self.p
s = (s2 + h * self.q) % self.n
return s
def verify_signature(self, digital_signature: int):
"""Use RSA public key to decrypt given Digital Signature"""
int_data = pow(digital_signature, self.e, self.n)
return uint_to_bytes(int_data)
Once the RSA class is completed, it is ready for testing. To test the basic encryption and decryption functions, first initialize an RSA object with the following parameters
\nNext, we can call the encryption method encrypt()
of the RSA object instance to encrypt the input message, and then feed the ciphertext to the decryption method decrypt()
and the fast decryption method decrypt_fast()
respectively. We use the assert
statement to compare the result with the original message. The code snippet is as follows.
# ---- Test RSA class ---- |
Likewise, we can also test the signature methods. In this case, we need to add the following import
statement to the beginning of the file
from hashlib import sha1 |
This allows us to generate the message digest with the library function sha1()
and then call the generate_signature()
and generate_signature_fast()
methods of the RSA object instance to generate the signature, respectively. Both signatures are fed to the verify_signature()` function and the result should be consistent with the original message digest. This test code is shown below.
mdg = sha1(msg).digest() |
If no AssertionError
is seen, we would get the following output, indicating that both the encryption and signature tests passed.
RSA message encryption/decryption test passes! |
Once the functional tests are passed, it is time to see how the performance of fast decryption is. We are interested in what speedup ratio we can achieve, which requires timing the execution of the code. For time measurements in Python programming, we have to import the functions urandom()
and timeit()
from the Python built-in libraries os
and timeit
, respectively:
from os import urandom |
urandom()
is for generaring random bype sequence, while timeit()
can time the execution of a given code segment. For the sake of convenience, the RSA decryption methods to be timed are first packed into two functions:
decrypt_norm()
- Regular decryption methoddecrypt_fast()
- Fast descryption methodBoth use the assert
statement to check the result, as shown in the code below:
def decrypt_norm(tester, ctxt: bytes, msg: bytes): |
The time code sets up two nested for
loops:
The outer loop iterates over different key lengths klen
, from 512 bits to 4096 bits in 5 levels, and the corresponding RSA object obj
is initialized with:
klen
The variable rpt
is also set in the outer loop to be the square root of the key length, and the timing variables t_n
and t_f
are cleared to zeros.
The inner layer also loops 5 times, each time executing the following operations:
\nurandom()
to generate a random sequence of bytes mg
with bits half the length of the keyobj.encrypt()
to generate the ciphertext ct
timeit()
and enter the packing functions decrypt_norm()
and decrypt_fast()
with the decryption-related parameters obj
, ct
and mg
, respectively, and set the number of executions to rpt
timeit()
function are stored cumulatively in t_n
and t_f
At the end of each inner loop, the current key length, the mean value of the timing statistics, and the calculated speedup ratio t_n/t_f
are printed. The actual program segment is printed below:
print("Start RSA fast decryption profiling...") |
Here are the results on a Macbook Pro laptop:
\nStart RSA fast decryption profiling... |
The test results confirm the effectiveness of the fast decryption method. As the key length increases, the computational intensity gradually increases and the running timeshare of the core decryption operation becomes more prominent, so the speedup ratio grows correspondingly. However, the final speedup ratio tends to a stable value of about 3.5, which is consistent with the upper bound of the theoretical estimate (\\(4-\\varepsilon\\)).
\nThe Python code implementation of the textbook RSA helps reinforce the basic number theory knowledge we have learned and also benefits us with an in-depth understanding of the RSA encryption algorithm. On this basis, we can also extend to experiment some RSA elementary attack and defense techniques to further master this key technology of public-key cryptography. For the complete program click here to download: textbook-rsa.py.gz
\nGary Lee Miller, a professor of computer science at Carnegie Mellon University, first proposed a deterministic algorithm based on the unproven generalized Riemann hypothesis. Later Professor Michael O. Rabin of the Hebrew University of Jerusalem, Israel, modified it to obtain an unconditional probabilistic algorithm.↩︎
This is because it follows from \\(x^2\\equiv 1\\pmod n\\) that \\((x-1)(x+1)=x^{2}-1\\equiv 0\\pmod n\\). Since \\(n\\) is a prime number, by Euclid's Lemma, it must divide either \\(x- 1\\) or \\(x+1\\), so \\(x\\bmod n\\) must be \\(1\\) or \\(-1\\).↩︎
RSA is a public-key cryptosystem built on top of an asymmetric encryption algorithm, which was jointly invented by three cryptographers and computer scientists at the Massachusetts Institute of Technology in 1977. The RSA public-key encryption algorithm and cryptosystem provide data confidentiality and signature verification functions widely used on the Internet. Since its birth, RSA has become a major research object of modern cryptography. Many cryptanalysts and information security experts have been studying its possible theoretical flaws and technical loopholes to ensure security and reliability in practical applications.
\nThere are certain things whose number is unknown. If we count them by threes, we have two left over; by fives, we have three left over; and by sevens, two are left over. How many things are there?
— Sunzi Suanjing, Volume 2.26
Fortunately, after more than 40 years of extensive research and practical application tests, although many sophisticated attack methods have been discovered, RSA is generally safe. These attack methods all take advantage of the improper use of RSA or the vulnerability of software and hardware implementations, and cannot shake the security foundation of its encryption algorithm. On the other hand, the research on these attack methods shows that implementing a safe and robust RSA application is not a simple task. A consensus in cryptography and network security hardware and software engineering practice is: never roll your own cryptography!1 The appropriate solution is to use an existing, well-tested, and reliably maintained library or API to implement the RSA algorithm and protocol application.
\nHere is a brief survey of the common means of attacking RSA, the mathematical mechanism on which the attack is based, and the corresponding protective measures. Referring to the previous article, let’s start by reviewing the working mechanism and process of RSA:
\nNote that the second and third steps in the original RSA paper use Euler's totient function \\(\\varphi(N)\\) instead of \\(\\lambda(N)\\). The relationship between these two functions is: \\[\\varphi(N)=\\lambda(N)⋅\\operatorname{gcd}(p-1,q-1)\\] Here \\(\\operatorname{gcd}\\) is the greatest common divisor function. Using \\(\\lambda(N)\\) can yield the minimum workable private exponent \\(d\\), which is conducive to efficient decryption and signature operations. Implementations that follow the above procedure, whether using Euler's or Carmichael's functions, are often referred to as \"textbook RSA \".
\nTextbook RSA is insecure, and there are many simple and effective means of attack. Before discussing the security holes of the textbook RSA in detail, it is necessary to review the first known attack method - integer factorization!
\nThe theoretical cornerstone of the security of the RSA encryption algorithm is the problem of factoring large numbers. If we can separate \\(p\\) and \\(q\\) from the known \\(N\\), we can immediately derive the private exponent \\(d\\) and thus completely crack RSA. Factoring large numbers is a presumed difficult computational problem. The best-known asymptotic running time algorithm is General Number Field Sieve, and its time complexity is \\({\\displaystyle \\exp \\left(\\left(c+o(1)\\right)(\\ln N)^{\\frac {1}{3}}(\\ln \\ln N)^{\\frac {2}{3}}\\right)}\\), where the constant \\(c = 4/\\sqrt[3]{9}\\),\\(\\displaystyle \\exp\\) and \\(\\displaystyle \\exp\\) is the exponential function of Euler's number (2.718).
\nFor a given large number, it is difficult to accurately estimate the actual complexity of applying the GNFS algorithm. However, based on the heuristic complexity empirical estimation, we can roughly see the increasing trend of computational time complexity:
\nThe rapid development of computer software and hardware technology has made many tasks that seemed impossible in the past become a reality. Check the latest record released by the RSA Factoring Challenge website. In February 2020, a team led by French computational mathematician Paul Zimmermann successfully decomposed the large number RSA-250 with 250 decimal digits (829 bits):
\nRSA-250 = 6413528947707158027879019017057738908482501474294344720811685963202453234463 |
announcement
\nAccording to the announcement of the factorization released by Zimmerman, using a 2.1GHz Intel Xeon Gold 6130 processor, the total computing time to complete this task is about 2700 CPU core-years. This number may seem large, but in today's era of cluster computing, grid computing, and cloud computing for the masses, it's not a stretch to think that organizations with strong financial backing can reduce computing time to hours or even minutes. As an example, go to the online tool website of the free open-source mathematical software system SageMath and enter the following first 5 lines of Sage Python code:
\np=random_prime(2**120) |
The result was obtained within minutes, and a large number of 72 decimal digits (240 bits) was decomposed. You know, in the 1977 RSA paper, it is mentioned that it takes about 104 days to decompose a 75-digit decimal number. The technological progress of mankind is so amazing!
\nAs the attacker's spear becomes sharper and sharper, the defender's shield must become thicker and thicker. Therefore, 1024-bit RSA is no longer secure, and applications should not use public key \\(N\\) values that are less than 2048 bits. And when high security is required, choose 4096-bit RSA.
\nAlthough the decomposition of large numbers is an attack method known to everyone, the security vulnerabilities caused by some low-level errors commonly found in RSA applications make it possible to use simple attacks to succeed, and some typical ones are explained below.
\nIn the early development of RSA, finding large prime numbers took quite a bit of time based on the backward computing power of the time. Therefore, some system implementations tried to share the modulus \\(N\\). The idea was to generate only one set \\((p,q)\\), and then all users would use the same \\(N=pq\\) values, with a central authority that everyone trusted assigning key pairs \\((e_i,d_i)\\) to each user \\(i\\), and nothing would go wrong as long as the respective private keys \\(d_i\\) were kept. Unfortunately, this is a catastrophic mistake! This implementation has two huge security holes:
\nThe user \\(i\\) can decompose \\(N\\) using his own key pair \\((e_i,d_i)\\). Whether \\(d\\) is generated using the Euler function \\(\\varphi(N)\\) or the Carmichael function \\(\\lambda(N)\\), there are algorithms that quickly derive the prime factors \\(p\\) and \\(q\\) from a given \\(d\\) 2. And once \\(p\\) and \\(q\\) are known, user \\(i\\) can compute any other user's private key \\(d_j\\) with one's public key \\((N,e_j)\\). At this point, the other users have no secrets from user \\(i\\).
Even if all users do not have the knowledge and skill to decompose \\(N\\), or are \"nice\" enough not to know the other users' private keys, a hacker can still perform a common modulus attack to break the users' messages. If the public keys of two users, Alice and Bob, are \\(e_1\\) and \\(e_2\\), and \\(e_1\\) and \\(e_2\\) happen to be mutually prime (which is very likely), then by Bézout's identity, the eavesdropper Eve can find that \\(s\\) and \\(t\\) satisfy: \\[e_{1}s+e_{2}t=gcd(e_1,e_2)=1\\] At this point, if someone sends the same message \\(m\\) to Alice and Bob, Eve can decrypt \\(m\\) after recording the two ciphertexts \\(c_1\\) and \\(c_2\\) and performing the following operation: \\[c_1^s⋅c_2^t\\equiv(m^{e _1})^s⋅(m^{e_2})^t\\equiv m^{e_{1}s+e_{2}t}\\equiv m\\pmod N\\] The corresponding Python function code is shown below.
\ndef common_modulus(e1, e2, N, c1, c2):
# Call the extended Euclidean algorithm function
g, s, t = gymp2.gcdext(e1, e2)
assert g == 1
if s < 0:
# Find c1's modular multiplicative inverse\t\t re = int(gmpy2.invert(c1, N))
c1 = pow(re, s*(-1), N)
c2 = pow(c2, t, N)
else:
# t is negative, find c2's modular multiplicative inverse
re = int(gmpy2.invert(c2, N))
c2 = pow(re, t*(-1), N)
c1 = pow(c1, a, N)
return (c1*c2) % N
Is it possible to reuse only \\(p\\) or \\(q\\) since the shared modulus \\(N\\) is proven to be insecure? This seems to avoid the common-modulus attack and ensure that each user's public key \\(N\\) value is unique. Big mistake! This is an even worse idea! The attacker gets the public \\(N\\) values of all users and simply combines \\((N_1,N_2)\\) pairwise to solve Euclid's algorithm for the great common divisor, and a successful solution gives a prime factor \\(p\\), and a simple division gives the other prime factor \\(q\\). With \\(p\\) and \\(q\\), the attacker can immediately compute the user's private key \\(d\\). This is the non-coprime modulus attack.
When applying textbook RSA, if both the public exponent \\(e\\) and the plaintext \\(m\\) are small, such that \\(c=m^e<N\\), the plaintext \\(m\\) can be obtained by directly calculating the \\(e\\)th root of the ciphertext \\(c\\). Even if \\(m^e>N\\) but not large enough, then since \\(m^e=c+k⋅N\\), you can loop through the small \\(k\\) values to perform brute-force root extraction cracking. Here is the Python routine:
\ndef crack_small(c, e, N, repeat)
times = 0
msg = 0
for k in range(repeat):
m, is_exact = gmpy2.iroot(c + times, e)
if is_exact and pow(m, e, N) == c:
msg = int(m)
break
times += N
return msg
Textbook RSA is deterministic, meaning that the same plaintext \\(m\\) always generates the same ciphertext \\(c\\). This makes codebook attack possible: the attacker precomputes all or part of the \\(m\\to c\\) mapping table and saves, then simply searches the intercepted ciphertext for a match. Determinism also means that textbook RSA is not semantically secure and that the ciphertext can reveal some information about the plaintext. Repeated occurrences of the ciphertext indicate that the sender is sending the same message over and over again.
Textbook RSA is malleable, where a particular form of algebraic operation is performed on the ciphertext and the result is reflected in the decrypted plaintext. For example, if there are two plaintexts \\(m_1\\) and \\(m_2\\), and encryption yields \\(c_1=m_1^e\\bmod N\\) and \\(c_2=m_2^e\\bmod N\\), what does \\((c_1⋅c_2)\\) decryption yield? Look at the following equation: \\[(c_1⋅c_2)^d\\equiv m_1^{ed}⋅m_2^{ed}\\equiv m_1⋅m_2\\pmod N\\] So the plaintext obtained after decrypting the product of the two ciphertexts is equal to the product of the two plaintexts. This feature is detrimental to RSA encryption systems in general and provides an opportunity for chosen-ciphertext attack. The following are two examples of attack scenarios:
\nImagine that there is an RSA decryption machine that can decrypt messages with an internally saved private key \\((N,d)\\). For security reasons, the decryptor will reject repeated input of the same ciphertext. An attacker, Marvin, finds a piece of ciphertext \\(c\\) that is rejected by the decryptor when he enters it directly because the ciphertext \\(c\\) has been decrypted before. Marvin finds a way to crack it. He prepares a plaintext \\(r\\) himself, encrypts it with the public key \\((N,e)\\) to generate a new ciphertext \\(c'={r^e}c\\bmod N\\), and then feeds the ciphertext \\(c'\\) to the decryptor. The decryption machine has not decrypted this new ciphertext, so it will not reject it. The result of the decryption is \\[m'\\equiv (c')^d\\equiv r^{ed}c^d\\equiv rm\\pmod N\\] Now that Marvin has \\(m'\\), he can calculate \\(m\\) using the formula \\(m\\equiv m'r^{-1}\\pmod N\\).
Suppose Marvin wants Bob to sign a message \\(m\\), but Bob refuses to do so after reading the message content. Marvin can achieve his goal by using an attack called blinding4. He picks a random message \\(r\\), generates \\(m'={r^e}m\\bmod N\\), and then takes \\(m'\\) to Bob to sign. Bob probably thinks \\(m'\\) is irrelevant and signs it. The result of Bob's signature is \\(s'=(m')^d\\bmod N\\). Now Marvin has Bob's signature on the original message \\(m\\) using the formula \\(s=s'r^{-1}\\bmod N\\). Why? The reason is that \\[s^e\\equiv (s')^er^{-e}\\equiv (m')^{ed}r^{-e}\\equiv m'r^{-e}\\equiv m\\pmod N\\]
The above is by no means a complete list of elementary attack methods, but they are illustrative. In practical RSA applications, we must be very careful and should do the following:
\nFor the textbook RSA deterministic and malleable flaws, and possible brute-force root extraction cracking vulnerabilities, the padding with random elements method can be used to protect against them, and the protection is valid due to the following:
\nUsing low public exponent is dangerous, and there are advanced attacks in the case of non-padding or improper padding, even if brute-force root extraction cracking does not succeed.
\nDiscovered by Swedish theoretical computer scientist Johan Håstad 5, hence the name Håstad's Broadcast Attack. Consider this simplified scenario, assuming that Alice needs to send the same message \\(m\\) to Bob, Carol, and Dave. The public keys of the three recipients are \\((N_1,3)\\), \\((N_2,3)\\), and \\((N_3,3)\\), i.e., the public exponent is all 3 and the public key modulus is different for each. The messages are not padded and Alice directly encrypts and sends three ciphertexts \\(c_1,c_2,c_3\\) using the public keys of the other three:
\n\\[\\begin{cases}\nc_1=m^3\\bmod N_1\\\\\nc_2=m^3\\bmod N_2\\\\\nc_3=m^3\\bmod N_3\n\\end{cases}\\]
\nAt this point Eve secretly writes down the three ciphertexts, marking \\(M=m^3\\), and if she can recover \\(M\\), running a cube root naturally yields the plaintext \\(m\\). Obviously, the common modulus attack does not hold here, and we can also assume that the moduli are pairwise coprime, or else decomposing the modulus using the non-coprime modulus attack will work. So does Eve have a way to compute \\(M\\)? The answer is yes.
\nIn fact, the equivalent problem for solving \\(M\\) here is: Is there an efficient algorithm for solving a number that has known remainders of the Euclidean division by several integers, under the condition that the divisors are pairwise coprime? This efficient algorithm is Chinese Remainder Theorem!
\nThe Chinese remainder theorem gives the criterion that a system of one-element linear congruence equations has a solution and the method to solve it. For the following system of one-element linear congruence equations (be careful not to confuse it with the mathematical notation used to describe the attack scenario above):
\n\\[(S) : \\quad \\left\\{ \n\\begin{matrix} x \\equiv a_1 \\pmod {m_1} \\\\\nx \\equiv a_2 \\pmod {m_2} \\\\\n\\vdots \\qquad\\qquad\\qquad \\\\\nx \\equiv a_n \\pmod {m_n} \\end\n{matrix} \\right.\\]
\nSuppose that the integers \\(m_1,m_2,\\ldots,m_n\\) are pairwise coprime, then the system of equations \\((S)\\) has a solution for any integer \\(a_1,a_2,\\ldots,a_n\\) and the general solution can be constructed in four steps as follows:
\n\\[\\begin{align}\nM &= m_1 \\times m_2 \\times \\cdots \\times m_n = \\prod_{i=1}^n m_i \\tag{1}\\label{eq1}\\\\\nM_i &= M/m_i, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{2}\\label{eq2}\\\\\nt_i M_i &\\equiv 1\\pmod {m_i}, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{3}\\label{eq3}\\\\\nx &=kM+\\sum_{i=1}^n a_i t_i M_i\\tag{4}\\label{eq4}\n\\end{align}\\]
\nThe last line above, Eq. (4) gives the formula of the general solution. In the sense of modulus \\(M\\), the unique solution is \\(\\sum_{i=1}^n a_i t_i M_i \\bmod M\\).
\nTry to solve the things whose number is unknown problem at the beginning of this article by using the Chinese remainder theorem
\nFirst, correspond the variable symbols to the values: \\[m_1=3,a_1=2;\\quad m_2=5,a_2=3;\\quad m_3=7,a_3=2\\] Then calculate \\(M=3\\times5\\times7=105\\), which in turn leads to the derivation of: \\[\\begin{align}\nM_1 &=M/m_1=105/3=35,\\quad t_1=35^{-1}\\bmod 3 = 2\\\\\nM_2 &=M/m_2=105/5=21,\\quad t_2=21^{-1}\\bmod 5 = 1\\\\\nM_3 &=M/m_3=105/7=15,\\quad t_3=15^{-1}\\bmod 7 = 1\\\\\n\\end{align}\\] Finally, take these into the general solution formula: \\[x=k⋅105+(2⋅35⋅2+3⋅21⋅1+2⋅15⋅1)=k⋅105+233\\] So the smallest positive integer solution concerning modulus 105 is \\(233\\bmod 105=23\\)。
\nIn his mathematical text \"Suanfa Tongzong\", Cheng Dawei, a mathematician of the Ming Dynasty in the 16th century, compiled the solutions recorded by the mathematician Qin Jiushao of the Song Dynasty in the \"Mathematical Treatise in Nine Sections\" into a catchy \"Sun Tzu's Song\":
\n\n\nThree friends set out with seventy rare
\n
\nTwenty-one blossoms on five trees of plums
\nSeven men reunited at the half-month
\nAll be known once divided by one hundred and five
Here we must admire the wisdom of the ancient Chinese who, in the absence of a modern mathematical symbol system, were able to derive and summarize such an ingenious solution, contributing an important mathematical theorem to mankind.
\n\nSo Eve just applies the solution of the Chinese Remainder Theorem, computes \\(M\\), and then finds its cube root to get the plaintext \\(m\\), and the attack succeeds. More generally, setting the number of receivers to \\(k\\), if all receivers use the same \\(e\\), then this broadcast attack is feasible as long as \\(k\\ge e\\).
\nHåstad further proves that even if padding is used to prevent broadcast attacks, if the messages generated by the padding scheme are linearly related to each other, such as using the formula \\(m_i=i2^b+m\\) (\\(b\\) is the number of bits of \\(m\\)) to generate the message sent to the receiver \\(i\\), then the broadcast attack can still recover the plaintext \\(m\\) as long as \\(k>e\\). The broadcast attack in this case is still based on the Chinese remainder theorem, but the specific cracking method depends on the information of the linear relationship.
\nTo summarize the above analysis, to prevent the broadcast attack, we must use a higher public exponent \\(e\\) and apply random padding at the same time. Nowadays, the common public key exponent \\(e\\) is 65537 (\\(2^{16}+1\\)), which can balance the efficiency and security of message encryption or signature verification operations.
\nLast, Python routines for simulating broadcast attacks are given as follows:
\ndef solve_crt(ai: list, mi: list): |
This code uses two methods to simulate the broadcast attack. One calls the generic Chinese remainder theorem solver function solve_crt()
and then gets the cube root of the result; the other calls the special broadcast attack function rsa_broadcast_attack()
for the public key index \\(e=3\\), which directly outputs the cracked plaintext value. The internal implementation of these two functions is based on the generalized formula of the Chinese remainder theorem, and the output results should be identical. The cracked plaintext value is then input to the uint_to_bytes()
function, which is converted into a byte array to compare with the original quote
. Note that the program uses objects generated by the RSA class to simulate the receivers Bob, Carroll, and Dave, and the implementation of the RSA class is omitted here given the limitation of space.
\n\nNext article: RSA: Attack and Defense (II)
\n
American computer scientist and security expert Gary McGraw has a famous piece of advice for software developers - \"never roll your own cryptography\"↩︎
The original RSA paper (Part IX, Section C) did mention Miller's algorithm for factoring \\(N\\) with a known \\(d\\). This algorithm also applies to \\(d\\) generated by the Carmichael function \\(\\lambda(N)\\).↩︎
gmpy2 is a Python extension module written in C that supports multi-precision arithmetic.↩︎
On some special occasions, blinding can be used for effective privacy protection. For example, in cryptographic election systems and digital cash applications, the signer and the message author can be different.↩︎
Johan Håstad, a Swedish theoretical computer scientist, a professor at the KTH Royal Institute of Technology, and a Fellow of the American Mathematical Society (AMS) and an Association for Computing Machinery (ACM) fellow.↩︎
In March 2021, the Internet Engineering Task Force (IETF) released RFC 8996, classified as a current best practice, officially announcing the deprecation of the TLS 1.0 and TLS 1.1 protocols. If your applications and web services are still using these protocols, please stop immediately and update to TLS 1.2 or TLS 1.3 protocol versions as soon as possible to eliminate any possible security risks.
\nOne single vulnerability is all an attacker needs.
— Window Snyder (American computer security expert, former Senior Security Strategist at Microsoft, and has been a top security officer at Apple, Intel and other companies)
The document title of RFC 8996 is quite straightforward, \"Deprecating TLS 1.0 and TLS 1.1\". So what is the rationale it gives? Here is a simple interpretation.
\nFirst, take a look at its abstract:
\n\n\nThis document formally deprecates Transport Layer Security (TLS) versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those documents have been moved to Historic status. These versions lack support for current and recommended cryptographic algorithms and mechanisms, and various government and industry profiles of applications using TLS now mandate avoiding these old TLS versions. TLS version 1.2 became the recommended version for IETF protocols in 2008 (subsequently being obsoleted by TLS version 1.3 in 2018), providing sufficient time to transition away from older versions. Removing support for older versions from implementations reduces the attack surface, reduces opportunity for misconfiguration, and streamlines library and product maintenance.
\nThis document also deprecates Datagram TLS (DTLS) version 1.0 (RFC 4347) but not DTLS version 1.2, and there is no DTLS version 1.1.
\nThis document updates many RFCs that normatively refer to TLS version 1.0 or TLS version 1.1, as described herein. This document also updates the best practices for TLS usage in RFC 7525; hence, it is part of BCP 195.
\n
The information given here is clear, the reasons for deprecating them are purely technical. TLS 1.0 and TLS 1.1 cannot support stronger encryption algorithms and mechanisms, and cannot meet the high-security requirements of various network applications in the new era. TLS is TCP-based. Corresponding to the UDP-based DTLS protocol, RFC 8996 also announced the deprecation of the DTLS 1.0 protocol.
\nThe Introduction section lists some details of the technical reasons:
\nClauses 5 and 6 above are clear and need no further explanation.
\nFor 3DES mentioned in Clause 1, although it uses three independent keys with a total length of 168 bits, considering the possible meet-in-the-middle_attack attack, its effective key strength is only 112 bits. Also, the 3DES encryption block length is still 64 bits, which makes it extremely vulnerable to birthday attack (see Sweet32). NIST stipulates that a single 3DES key group can only be used for encrypting \\(2^{20}\\) data blocks (ie 8MB). This was of course too small, and eventually, NIST decided in 2017 to deprecate 3DES in the IPSec and TLS protocols.
\n3DES is just one example, another category that has been phased out earlier is cipher suites that use RC4 stream ciphers, see RFC 7465 for details. In addition, there are various problems in the implementation of block cipher CBC mode, which are often exploited by attackers to crack TLS sessions. A summary of various attacks and countermeasures of TLS 1.0 and TLS 1.1 is described in detail in NIST800-52r2 and RFC7457. These two reference documents provide the key rationale for deprecation. Obviously, any protocol that mandates the implementation of insecure cipher suites should be on the list to be eliminated.
\nIn the second section of the document, the content in Section 1.1 \"The History of TLS\" of NIST800-52r2 is directly quoted (abbreviated as shown in the following table):
\nTLS Version | \nProtocol Document | \nKey Feature Update | \n
---|---|---|
1.1 | \nRFC 4346 | \nImproved initialization vector selection and padding error processing to address weaknesses discovered on the CBC mode of operation defined in TLS 1.0. | \n
1.2 | \nRFC 5246 | \nEnhanced encryption algorithms, particularly in the area of hash functions, can support SHA-2 series algorithms for hashing, MAC, and pseudorandom function computations, also added AEAD cipher suite. | \n
1.3 | \nRFC 8446 | \nA significant change to TLS that aims to address threats that have arisen over the years. Among the changes are a new handshake protocol, a new key derivation process that uses the HMAC-based Extract-and-Expand Key Derivation Function (HKDF), and the removal of cipher suites that use RSA key transport or static Diffie-Hellman key exchanges, the CBC mode of operation, or SHA-1. | \n
AEAD is an encryption mode that can guarantee the confidentiality, integrity, and authenticity of data at the same time, typically such as CCM and GCM. TLS 1.2 introduced a range of AEAD cipher suites, and its high security made it the exclusive choice for TLS 1.3. These annotate Clause 2 of technical reasons.
\nClauses 3 and 4 of technical reasons call out SHA-1, so what is the problem with SHA-1? Section 3 of the document cites a paper by two French researchers, Karthikeyan Bhargavan and Gaetan Leurent .
\nAs a cryptographic hash function, SHA-1 was designed by the National Security Agency (NSA) and then published as a Federal Information Processing Standard (FIPS) by the National Institute of Standards and Technology (NIST). SHA-1 can process a message up to \\(2^{64}\\) bits and generate a 160-bit (20-byte) hash value known as the message digest. Therefore, the complexity of brute force cracking based on birthday attack is \\(2^{80}\\) operations. In 2005, Chinese cryptographer Wang Xiaoyun and her research team made a breakthrough in this field. The high-efficiency SHA-1 attack method they published can be used to find a hash collision within a computational complexity of \\(2^{63}\\). This has brought a huge impact on the security of SHA-1, but it does not mean that the cracking method can enter the practical stage.
\nNetwork security protocols (such as TLS, IKE, and SSH, etc.) rely on the second preimage resistance of cryptographic hash functions, that is, it is computationally impossible to find any secondary input value that has the same output as a specific input value. For example, for a cryptographic hash function \\(h(x)\\) and given input \\(x\\), it is difficult to find a sub-preimage \\(x^′ ≠ x\\) that is satisfying \\(h(x) = h(x^′)\\). Because finding a hash collision does not mean that a sub-preimage can be located, in practice, it was once thought that continuing to use SHA-1 is not a problem.
\nHowever, in 2016, Bhargavan and Leurent (who implemented the aforementioned Sweet32 attack against 64-bit block ciphers) discovered a new class of methods to attack key exchange protocols that shattered this perception. These methods are based on the principle of the chosen prefix collision attack. That is, given two different prefixes \\(p_1\\) and \\(p_2\\), the attack finds two appendages \\(m_1\\) and \\(m_2\\) such that \\(h(p_1 ∥ m_1) = hash(p_2 ∥ m_2)\\). Using this approach, they demonstrated a man-in-the-middle attack against TLS clients and servers to steal sensitive data, and also showed that the attack could be used to masquerade and downgrade during TLS 1.1, IKEv2, and SSH-2 session handshakes. In particular, they proved that with only \\(2^{77}\\) operations the handshake protocol using SHA-1 or MD5 and SHA-1 concatenated hash values could be cracked.
\nSince neither TLS 1.0 nor TLS 1.1 allows the peers to choose a stronger cryptographic hash function for signatures in the ServerKeyExchange or CertificateVerify messages, the IETF confirmed that using a newer protocol version is the only upgrade path.
\nSections 4 and 5 of the document again clarify that TLS 1.0 and TLS 1.1 must not be used, and negotiation to TLS 1.0 or TLS 1.1 from any TLS version is not allowed. This means that ClientHello.client_version and ServerHello.server_version issued by the TLS client and server, respectively, must not be {03,01} (TLS 1.0) or {03,02} (TLS 1.1). If the protocol version number in the Hello message sent by the other party is {03,01} or {03,02}, the local must respond with a \"protocol_version\" alert message and close the connection.
\nIt is worth noting that due to historical reasons, the TLS specification does not specify the value of the record layer version number (TLSPlaintext.version) when the client sends the ClientHello message. So to maximize interoperability, TLS servers MUST accept any value {03,XX} (including {03,00}) as the record layer version number for ClientHello messages, but they MUST NOT negotiate TLS 1.0 or 1.1.
\nSection 6 of the document declares a textual revision to the previously published RFC 7525 (Recommendations for the Secure Use of TLS and DTLS). Three places in this RFC change implementation-time negotiations of TLS 1.0, TLS 1.1, and DTLS 1.0 from \"SHOULD NOT\" to \"MUST NOT\". The last section is a summary of standard RFC operations and security considerations.
\nIn the industry of large public online services, GitHub was the first to act. They started disabling TLS 1.0 and TLS 1.1 in all HTTPS connections back in February 2018, while also phasing out insecure diffie-hellman-group1-sha1
and diffie-hellman-group14-sha1
key exchange algorithms in the SSH connection service. In August 2018, Eric Rescorla, CTO of Mozilla Firefox, published the TLS 1.3 technical specification RFC 8446. Two months later, Mozilla issued a statement together with the three giants of Apple, Google, and Microsoft, and put the deprecation of TLS 1.0 and TLS 1.1 on the agenda.
The following is a brief summary of the actions of several related well-known companies:
\nSecurity.framework
symbols from the app\nBoth TLS/DTLS clients and servers need to be tested to verify that their implementations follow the current best practices of RFC 8996.
\nQualys originated as a non-commercial SSL Labs Projects. They offer a free and simple client and server testing service, as well as a monitoring panel reporting TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics for November 2022.
\nProtocol Version | \nSecurity | \nSupporting Sites (Oct. 2022) | \nSupporting Site (Nov. 2022) | \n% Change | \n
---|---|---|---|---|
SSL 2.0 | \nInsecure | \n316(0.2%) | \n303(0.2%) | \n-0.0% | \n
SSL 3.0 | \nInsecure | \n3,015(2.2%) | \n2,930(2.2%) | \n-0.0% | \n
TLS 1.0 | \nDeprecated | \n47,450(34.9%) | \n46,691(34.4) | \n-0.5% | \n
TLS 1.1 | \nDeprecated | \n51,674(38.1%) | \n50,816(37.5%) | \n-0.6% | \n
TLS 1.2 | \nDepending on the Cipher Suite and the Client | \n135,557(99.8) | \n135,445(99.9) | \n+0.1% | \n
TLS 1.3 | \nSecure | \n78,479(57.8%) | \n79,163(58.4%) | \n+0.6% | \n
As you can see, almost 100% of sites are running TLS 1.2, and the percentage of TLS 1.3 support is close to 60%. This is very encouraging data. While very few sites are still running SSL 2.0/3.0 and TLS 1.0/1.1 are both still supported at around 35%, overall their percentages are continuing to decline and this good trend should continue.
\nThis blog site is served by GitHub Page, enter the URL to SSL Server Test page and submit it to get a summary of the test results as follows.
\nThe site achieved the highest overall security rating of A+. It got a perfect score for certificate and protocol support, and a 90 for both key exchange and password strength. This shows that GitHub fulfills its security promises to users and deserves the trust of programmers.
\nThe configuration section of the report gives details of the test results for protocol support and cipher suites as follows.
\nThis further confirms that the GitHub Page only supports TLS 1.2/1.3, as required by RFC 8996. It can also be seen that under the \"Cipher Suites\" subheading, TLS 1.3 shows two GCMs and one ChaCha20-Poly1305, which are all cipher suites based on the AEAD algorithms. Three cipher suites of the same type are the preferred TLS 1.2 cipher suites for the server as well. This is exactly the current commonly adopted configuration of secure cryptographic algorithms.
\nIf you suspect that a private server is still using the outdated TLS/SSL protocol, you can do a simple test with the command line tool curl
, an example of which is as follows.
❯ curl https://www.cisco.com -svo /dev/null --tls-max 1.1 |
Here enter the command line option -tls-max 1.1
to set the highest protocol version 1.1 and connect to the Cisco home page. The output shows that the connection failed and that a \"protocol version\" alert message was received. This indicates that the server has rejected the TLS 1.1 connection request, and the response is exactly what is required by RFC 8996.
The openssl
command line tool provided by the general purpose open source cryptography and secure communication toolkit OpenSSL can also do the same test. To test whether the server supports the TLS 1.2 protocol, use the option s_client
to emulate a TLS/SSL client and also enter -tls1_2
to specify that only TLS 1.2 is used. The command line runs as follows.
❯ openssl s_client -connect www.cisco.com:443 -tls1_2 |
This record is very detailed and the format is very readable. From the output, it can be understood that the digital certificate of the Cisco home page server is digitally signed and certified by the root certificate authority IdenTrust. The client-server session is built on the TLS 1.2 protocol, and the selected cipher suite is ECDHE-RSA-AES128-GCM-SHA256 of type AEAD, which is identical to the preferences provided by the GitHub Page.
\nIf you are not sure about the security of your browser and want to test whether it still supports the pre-TLS 1.2 protocols, you can enter the following URL in your browser's address bar.
\nAfter connecting to the second URL with the default configuration of Firefox, the page shows the following
\n\n\nSecure Connection Failed
\nAn error occurred during a connection to tls-v1-1.badssl.com:1011. Peer using unsupported version of security protocol.
\nError code: SSL_ERROR_UNSUPPORTED_VERSION
\n\n
\n- The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
\n- Please contact the website owners to inform them of this problem.
\nThis website might not support the TLS 1.2 protocol, which is the minimum version supported by Firefox.
\n
This error message clearly indicates that Firefox is running a minimum TLS protocol version of 1.2 in this configuration, and since the other side is only running TLS 1.1, the two sides cannot establish a connection.
\nSo what is the result of the connection when the browser does still retain TLS 1.0/1.1 functionality?
\nFor testing purposes, you can first change the default TLS preference value of Firefox to 1.1 by following the steps below (refer to the figure below).
\nAt this point, then connect to https://tls-v1-1.badssl.com, the result is
\nThis bold red page tells you that the browser you are currently using does not have TLS 1.1 disabled and is a security risk, so try not to use it if you can.
\nAfter testing, don't forget to restore the default TLS minimum version setting (3) for Firefox.
\n\n\nDisclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.
\n
Besides NIST and RFC documents, For an in-depth study of the TLS protocol specification, system implementation, and application deployment, a careful reading of the following three books is recommended.
\n\nTLS (Transport Layer Security) is a cryptographic protocol to secure network communication. TLS 1.3 is the latest version of the TLS protocol, succeeding TLS 1.2. TLS 1.3 aims to provide more robust security, higher privacy protection, as well as better performance than previous versions. Here is a brief introduction to TLS 1.3. Also, we discuss NIST's requirement for TLS 1.3 readiness and give examples of enabling TLS 1.3 in some commonly used web servers.
\nIt takes 20 years to build a reputation and a few minutes of cyber-incident to ruin it.
— Stéphane Nappo (Vice President and Global Chief Information Security Officer of Groupe SEB, France, 2018 Global CISO of the year)
TLS 1.3 is the latest recommended cryptographic protocol for protecting a wide variety of network communications, including web browsing, email, online trading, instant messaging, mobile payments, and many other applications. By using TLS 1.3, more secure and reliable communication connections can be established, ensuring confidentiality, authenticity, and data integrity. It was standardized by the Internet Engineering Task Force (IETF) in August 2018, and published as RFC 8446.
\nTLS 1.3 introduces some important improvements over TLS 1.2. The table below presents a quick comparison of the two:
\nAspect | \nTLS 1.2 | \nTLS 1.3 | \n
---|---|---|
Protocol Design | \nRequest-response model | \nReduced round trips | \n
Handshake | \nMultiple round trips | \nSingle round trip | \n
Cipher Suites | \nSupports wide range, including insecure ones | \nFocuses on stronger algorithms | \n
Security | \nKnown vulnerabilities, e.g., CBC vulnerabilities | \nAddresses previous issues, stronger security | \n
Performance | \nHigher latency due to more round trips | \nFaster connection establishment | \n
Resilience to Attacks | \nVulnerable to downgrade attacks and padding oracle attacks | \nAdditional protections against attacks | \n
Compatibility | \nWidely supported across platforms | \nIncreasing support, may not be available on older systems | \n
Implementation Supports | \nAvailable in many cryptographic libraries | \nSupported in various libraries | \n
It can be seen that enhanced security and performance improvements are the most notable features of TLS 1.3, and we can explore more into these in the following sections.
\nThe protocol design principle of TLS 1.3 has enhanced security as its primary goal. As a result, TLS 1.3 drastically reduces the number of supported cipher suites. It removes insecure and weak cipher suites, leaving only more secure and modern cipher suites. This helps to increase the security of communications and avoids the use of outdated or vulnerable cipher suites.
\nSpecifically, TLS 1.3 removes various cipher suites that use static RSA key transport, static Diffie-Hellman key exchange, CBC mode of operation, or SHA-1. It adopts only a limited number of Authenticated Encryption with Associated Data (AEAD) cipher suites. AEAD can guarantee the confidentiality, integrity, and authenticity of data at the same time, and its high security makes it the exclusive choice for TLS 1.3.
\nOn the other hand, the name string of the cipher suite used in previous TLS versions included all algorithms for key exchange, digital signatures, encryption, and message authentication. Each cipher suite is assigned a 2-byte code point in the TLS Cipher Suites registry managed by the Internet Assigned Numbers Authority (IANA). Every time a new cryptographic algorithm is introduced, a series of new combinations need to be added to the list. This has led to an explosion of code points representing every valid choice of these parameters. This situation also makes the selection of cipher suites complicated and confusing.
\nThe design of TLS 1.3 changed the concept of the cipher suite. It separates the authentication and key exchange mechanisms from the record protection algorithm (including secret key length) and a hash to be used with both the key derivation function and handshake message authentication code (MAC). The new cipher suite naming convention is TLS_<AEAD>_<Hash>
, where the hash algorithm is used for the newly defined key derivation function HKDF of TLS 1.3 and the MAC generation in the handshake phase. The cipher suites defined by the TLS 1.3 protocol are:
+------------------------------+-------------+ |
This simplified cipher suite definition and greatly reduced set of negotiation parameters also speed up TLS 1.3 handshake, improving overall performance.
\nTLS 1.3 emphasizes forward secrecy, ensuring that the confidentiality of communications is protected even if long-term secrets used in the session key exchange are compromised. It only allows key exchange based on ephemeral Diffie-Hellman key exchange (DHE) or ephemeral elliptic curve Diffie-Hellman key exchange (ECDHE). Both have the property of forward secrecy. Also, the protocol explicitly restricts the use of secure elliptic curve groups and finite field groups for key exchange:
\n/* Elliptic Curve Groups (ECDHE) */ |
The above elliptic curve groups for ECDHE are specified by RFC 8422. The first three are defined by the FIPS.186-4 specification and the corresponding NIST names are P-256/P-384/P-512, while the next two (x25519/x448) are recommended by ANSI.X9-62.2005. RFC 7919 specifies four finite field groups (ffdhe####) for DHE. The primes in these finite field groups are all safe primes.
\nIn number theory, a prime number \\(p\\) is a safe prime if \\((p-1)/2\\) is also prime.
\nFor signature verification in the key exchange phase, TLS 1.3 introduces more signature algorithms to meet different security requirements:
\nTLS 1.3 stops using the DSA (Digital Signature Algorithm) signature algorithm. This is also a notable difference from TLS 1.2. DSA has some security and performance limitations and is rarely used in practice, so TLS 1.3 removed support for DSA certificates.
\nAdditionally, TLS 1.3 includes the following improvements to enhance security
\nServerHello
message during the TLS 1.3 handshake are now encrypted. The newly introduced EncryptedExtensions
message enables encryption protection of various extensions previously sent in plain text.Certificate
messages sent from the server to the client. This encryption prevents threats such as man-in-the-middle attacks, information leakage, and certificate forgery, further fortifying the security and privacy of the connection.The general trend towards high-speed mobile Internet requires the use of HTTPS/TLS to protect the privacy of all traffic as much as possible. The downside of this is that new connections can become a bit slower. For the client and web server to agree on a shared key, both parties need to exchange security attributes and related parameters through the TLS \"handshake process\". In TLS 1.2 and all protocols before it, the initial handshake process required at least two round-trip message transfers. Compared to pure HTTP, the extra latency introduced by the TLS handshake process of HTTPS can be very detrimental to performance-conscious applications.
\nTLS 1.3 greatly simplifies the handshake process, requiring only one round trip in most cases, resulting in faster connection establishment and lower latency. Every TLS 1.3 connection will use (EC)DHE-based key exchange, and the parameters supported by the server may be easy to guess (such as ECDHE + x25519 or P-256). Since the options are limited, the client can directly send the (EC)DHE key share information in the first message without waiting for the server to confirm which key exchange it is willing to support. This way, the server can derive the shared secret one round in advance and send encrypted data.
\nThe following diagram compares the message sequences of the handshake process of TLS 1.2 and TLS 1.3. Both operate with public key-based authentication. The TLS 1.3 handshake shown below uses the symbols borrowed from the RFC 8446 specification: '+' indicates a noteworthy extension; '*' indicates an optional message or extension; '[]', '()', and '{}' represent encrypted messages, where the keys used for encryption are different.
\nThis figure illustrates the following points:
\nServerHelloDone
, ChangeCipherSpec
, ServerKeyExchange
, and ClientKeyExchange
. The contents of TLS 1.2's ServerKeyExchange
and ClientKeyExchange
messages vary depending on the authentication and key-sharing method being negotiated. In TLS 1.3, this information was moved to the extensions of ClientHello
and ServerHello
messages. TLS 1.3 completely deprecates ServerHelloDone
and ChangeCipherSpec
messages, there is no replacement.ClientHello
message carries four extensions that are must-haves in this mode: key_share
, signature_algorithms
, supported_groups
, and support_versions
.ClientKeyExchange
and ChangeCipherSpec
messages are carried in separate packets, and the Finished
message is the first (and only) encrypted handshake message. The whole process needs to transmit 5-7 data packets.Application Data
is already sent by the client after the first round trip. As mentioned earlier, the EncryptedExtension
message provides privacy protection for ServerHello
extensions in earlier versions of TLS. If mutual authentication is required (which is common in IoT deployments), the server will send a CertificateRequest
message.Certificate
, CertificateVerify
, and Finished
messages in TLS 1.3 retain the semantics of earlier TLS versions, but they are all asymmetrically encrypted now. Echoing the description in the last section, by encrypting Certificate
and CertificateVerify
messages, TLS 1.3 better protects against man-in-the-middle and certificate forgery attacks while enhancing the privacy of connections. This is also an important security feature in the design of TLS 1.3.In rare cases, when the server does not support a certain key-sharing method sent by the client, the server can send a new HelloRetryRequest
message letting the client know which groups it supports. As the group list has shrunk significantly, this is not expected to happen very often.
0-RTT (Zero Round Trip Time) in TLS 1.3 is a special handshake mode. It allows clients to send encrypted data during the handshake phase, reducing the number of round trips required for connection establishment and enabling faster session resumption. The following is a brief explanation of the 0-RTT working mode:
\nearly_data
extension of the ClientHello
message, along with encrypted Application Data
. The client encrypts 0-RTT data using a pre-shared key (PSK) obtained from a previous connection.EncryptedExtensions
message, and then confirms the connection in the Finished
message. This way, the server can quickly establish a secure connection with 0 round trips. It can also immediately send data to the client to achieve 0-RTT data transmission.The message sequence of the 0-RTT session resumption and data transmission process of TLS 1.3 is as follows:
\nDoes the TLS 1.3 protocol allow the use of RSA digital certificates?
\nA common misconception is that \"TLS 1.3 is not compatible with RSA digital certificates\". The description in the \"Signature Verification\" section above shows that this is wrong. TLS 1.3 still supports the use of RSA for key exchange and authentication. However, considering the limitations of RSA, it is recommended that when building and deploying new TLS 1.3 applications, ECDHE key exchange algorithms and ECC digital certificates are preferred to achieve higher security and performance.
During the TLS 1.3 handshake, how does the server request the client to provide a certificate?
\nIn some scenarios, the server also needs to verify the identity of the client to ensure that only legitimate clients can access server resources. This is the case with mTLS (mutual TLS). During the TLS 1.3 handshake, the server can specify that the client is required to provide a certificate by sending a special CertificateRequest
extension. When the server decides to ask the client for a certificate, it sends a CertificateRequest
extension message after the ServerHello
message. This extended message contains some necessary parameters, such as a list of supported certificate types, a list of acceptable certificate authorities, and so on. When the client receives it, it knows that the server asked it for a certificate, and it can optionally respond to the request. If the client is also configured to support mTLS and decides to provide a certificate, it provides its certificate chain by sending a Certificate
message.
Is 0-RTT vulnerable to replay attacks?
\nTLS 1.3's 0-RTT session resumption mode is non-interactive and does risk replay attacks in some cases. An attacker may repeat previously sent data to simulate a legitimate request. To avoid and reduce the risk of replay attacks to the greatest extent, TLS 1.3 provides some protection measures and suggestions:
\nClientHello
message, and reject duplicates. Logging all ClientHello
s would cause the state to grow without bound, but combined with #2 above, the server can log ClientHello
s within a given time window and use obfuscated_ticket_age
to ensure that tickets are not duplicated outside the window use.If the client does not know whether the server supports TLS 1.3, how could it negotiate the TLS version via handshake?
\nThe TLS protocol provides a built-in mechanism for negotiating the running version between endpoints. TLS 1.3 continues this tradition. RFC 8446 Appendix D.1 \"Negotiating with an Older Server\" gives specific instructions:
\n\n\nA TLS 1.3 client who wishes to negotiate with servers that do not support TLS 1.3 will send a normal TLS 1.3 ClientHello containing 0x0303 (TLS 1.2) in ClientHello.legacy_version but with the correct version(s) in the \"supported_versions\" extension. If the server does not support TLS 1.3, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation will proceed as appropriate for the negotiated protocol.
\n
The following screenshot of a TLS 1.3 ClientHello
message decode demonstrates this. The version number of the handshake message displayed on the left is \"Version: TLS 1.2 (0x0303)\". At the same time, it can be seen that the cipher suite section first lists 3 TLS 1.3 AEAD cipher suites, followed by 14 TLS 1.2 regular cipher suites. On the right, there are 4 extensions - key_share
, signature_algorithms
, supported_groups
, and support_versions
. The support_versions
extension includes both TLS 1.3 and TLS 1.2 version numbers. This is the TLS version list for the server to choose from. Additionally, the key_share
extension includes the client's preferred key-sharing method as x25519 and secp256r1(i.e. NIST P-256)
Does the TLS 1.3 protocol work with UDP and EAP?
\nTLS was originally designed for TCP connections, and a variant DTLS (Datagram Transport Layer Security) for UDP was introduced later. Based on TLS 1.3, IETF has released the corresponding upgraded version of the DTLS 1.3 protocol RFC 9147. The design goal of DTLS 1.3 is to provide \"equivalent security guarantees with the exception of order protection / non-replayability\". This protocol was released in April 2022, and currently, there are not many software libraries supporting it.
\nTLS can also be used as an authentication and encryption protocol in various EAP types, such as EAP-TLS, EAP-FAST, and PEAP. Corresponding to TLS 1.3, IETF also published two technical standard documents:
\nBoth protocols are also quite new, and the software library updates supporting them are still some time away.
TLS 1.3 brings new security features and a faster TLS handshake. Since its release in 2018, many Internet services have migrated to this latest version. Nevertheless, widespread adoption across websites takes time. The non-commercial SSL Labs Projects has a dashboard called SSL Pulse that reports TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics by July 2023.
\nAs can be seen, of all 135,000+ probed sites the percentage of TLS 1.3 support is about 63.5%. That means there are still close to 50 thousand sites that do not leverage the security and performance benefits of TLS 1.3. Why? The decision to migrate a website to a new protocol version like TLS 1.3 can be complex and influenced by various factors. The top 3 common reasons hindering TLS 1.3 migration are
\nHowever, for network hardware/software vendors who want their products on the procurement list of any US public sector organization, there is a coming NIST mandate to make TLS 1.3 available by January 2024. This is stipulated in the National Institute of Standards and Technology Special Publication (NIST SP) 800-52 Rev. 2: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations. Quoted from NIST SP 800-52 Rev. 2
\n\n\n3.1 Protocol Version Support
\nServers that support government-only applications shall be configured to use TLS 1.2 and should be configured to use TLS 1.3 as well. ...
\nServers that support citizen or business-facing applications (i.e., the client may not be part of a government IT system)10 shall be configured to negotiate TLS 1.2 and should be configured to negotiate TLS 1.3. ...
\nAgencies shall support TLS 1.3 by January 1, 2024. After this date, servers shall support TLS 1.3 for both government-only and citizen or business-facing applications. In general, servers that support TLS 1.3 should be configured to use TLS 1.2 as well. However, TLS 1.2 may be disabled on servers that support TLS 1.3 if it has been determined that TLS 1.2 is not needed for interoperability.
\n
As in the RFC documents, \"shall\" above is a strong keyword that means that the definition is an absolute requirement of the specification. So this NIST publication requires all servers owned by the US government agencies to be able to support TLS 1.3 by 01/01/2024. They must run a minimum TLS version 1.2 by default and can be configured to do TLS 1.3 only if desired.
\nIt is worth pointing out that this is not an official FIPS requirement, so not mandatory for the FIPS 140-3 certification at present. Besides, this NIPS document has a clear scope statement: \"The scope is further limited to TLS when used in conjunction with TCP/IP. For example, Datagram TLS (DTLS), which operates over datagram protocols, is outside the scope of these guidelines. NIST may issue separate guidelines for DTLS at a later date.\" Based on this, we can infer that DTLS and EAP are out of consideration for this mandate.
\nThe enhanced security and optimized performance of TLS 1.3 make it the first choice for securing communication of various network applications. Now we demonstrate how to enable TLS 1.3 function in three commonly used web server software Apache, Nginx, and Lighttpd.
\nNOTE: The implementation of many secure network communication applications relies on third-party SSL/TLS software libraries, such as wolfSSL, GnuTLS, NSS, and OpenSSL. Therefore, to enable the TLS 1.3 function of these applications, you need to ensure that the libraries they link with support TLS 1.3. For example, in September 2018, the popular OpenSSL project released version 1.1.1 of the library, with support for TLS 1.3 as its \"top new feature\".
\nThe Apache HTTP Server is an open-source web server software from the Apache Software Foundation. Apache HTTP server is widely used and is one of the most popular web server software due to its cross-platform and security. Apache supports a variety of features, many of which extend core functionality through compiled modules, such as authentication schemes, proxy servers, URL rewriting, SSL/TLS support, and compiling interpreters such as Perl/Python into the server.
\nApache HTTP Server has built-in support for TLS 1.3 since version 2.4.36, no need to install any additional modules or patches. The following command can be used to verify the version of the server
\n$ apache2ctl -v |
Once the version is verified, the SSLProtocol
line of the configuration file can be updated. The following will enable the Apache HTTP server to only support the TLS 1.3 protocol
# Only enable TLS 1.3 |
If the server needs to be compatible with clients that support TLS 1.2, you can add +TLSv1.2
. After updating the configuration, restart the service
$ sudo service apache2 restart |
Nginx is a high-performance web server based on an asynchronous framework and modular design. It can also be used for reverse proxy, load balancer, and HTTP caching applications. It is free and open-source software released under the terms of a BSD-like license. Nginx uses an asynchronous event-driven approach to request processing, which can provide more predictable performance under high load. The current market share of Nginx is almost equal to that of the Apache HTTP server.
\nNginx supports TLS 1.3 from version 1.13.0. The following command can be used to verify its version
\n$ nginx -v |
In the Nginx configuration file, find the server block and modify the ssl_protocols
line to enable TLS 1.3:
server { |
If you don't need to continue to support TLS 1.2, delete the TLSv1.2
there. After the modification is complete, you can run the following command to test the configuration of Nginx, and then restart the service
$ sudo nginx -t |
Lighttpd is a lightweight open-source web server software. It focuses on high performance, low memory footprint, and fast responsiveness. Lighttpd is suitable for serving web applications and static content of all sizes. Its design goal is to provide an efficient, flexible, and scalable web server, especially suitable for high-load and resource-constrained (such as embedded systems) environments.
\nThe first Lighttpd release to support TLS 1.3 is version 1.4.56. Starting with this version, the minimum version of TLS that Lighttpd supports by default is TLS 1.2. That is to say, Lighttpd supports TLS 1.2 and TLS 1.3 if no corresponding configuration file modification is made.
\nTo limit the use of Lighttpd to only the TLS 1.3 feature, first make sure the mod_openssl module is loaded. Then in the configuration file lighttpd.conf, find the server.modules
section, and add the following ssl.openssl.ssl-conf-cmd
line:
server.modules += ("mod_openssl") |
This will set the minimum version supported by Lighttpd to be TLS 1.3. Finally, save and reload the Lighttpd configuration for the changes to take effect:
\nsudo lighttpd -t -f /etc/lighttpd/lighttpd.conf # check configuration |
uClibc is a small and exquisite C standard library for embedded Linux systems. It is widely used in the development of low-end embedded systems and Internet of Things devices. Here are some recent experiences to provide convenience for engineers who need to solve similar problems or meet corresponding requirements.
\nLow-level programming is good for the programmer's soul.
— John Carmack (American computer programmer and video game developer, co-founder of the video game company id Software)
uClibc (sometimes written as μClibc) is a small C standard library designed to provide support for embedded systems and mobile devices using operating systems based on the Linux kernel. uClibc was originally developed to support μClinux, a version of Linux not requiring a memory management unit thus especially suited for microcontroller systems. The \"uC\" in its name is the abbreviation of microcontroller in English, where \"u\" is a Latin script typographical approximation of the Greek letter μ that stands for \"micro\".
\nuClibc is a free and open-source software licensed under the GNU Lesser GPL, and its library functions encapsulate the system calls of the Linux kernel. It can run on standard or MMU-less Linux systems and supports many processors such as i386, x86-64, ARM, MIPS, and PowerPC. Development of uClibc started in 1999 and was written mostly from scratch, but also absorbed code from glibc and other projects. uClibc is much smaller than glibc. While glibc aims to fully support all relevant C standards on a wide range of hardware and kernel platforms, uClibc focuses on embedded Linux systems. It also allows developers to enable or disable some features according to the memory space design requirements.
\nThe following records show the list of C standard library files in two similar embedded systems. The first uses glibc-2.23 version, and the second integrates uClibc-0.9.33.2 version. The total size of glibc library files is more than 2MB, while the uClibc library files add up to less than 1MB. It can be seen that using uClibc does save a lot of storage space.
\nSTM1:/# find . -name "*lib*2.23*" | xargs ls -alh |
With the steady growth of IPv6 deployment, adding IPv6 protocol stack support for embedded systems has become necessary. In a software project that adds IPv4/IPv6 dual-stack function to devices using uClibc, it is found that there is an application link error - undefined reference to getifaddrs
. getifaddrs()
is a very useful function, we can call it to get the address information of all the network interfaces of the system. Query the Linux programming manual:
SYNOPSIS |
The last sentence above is key: only kernels supporting netlink can support address families other than IPv4. The Linux kernel version running on this system is 3.x, which supports netlink. So, could there be a problem with uClibc's support for netlink that causes getifaddrs() not to get compiled?
\nWith this question in mind, search the source code directory of uClibc and find the C file that implements the function getifaddrs()
:
... |
Just as expected! The implementation of the entire function and the definition of the associated data structure ifaddrs_storageare are placed under three nested conditional compilation directives with macros defined as
\nTherefore, as long as their corresponding configuration lines are opened, the problem should be solved. After changing the configuration file of uClibc as follows, rebuild the dynamic link library of uClibc, then the application can be made successfully:
\n--- a/toolchain/uClibc/config-0.9.33.2/common |
Embedded systems often need to provide remote SSH login services for system administrators, which requires the creation of system users and their passwords. Linux saves the user name and the hashed password in the /etc/shadow file. The storage format of the hash value follows a de facto standard called the Modular Crypt Format (MCF for short), and its format is as follows:
\n$<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
Here
\nWith the rapid increase of computing power following Moore's Law, the previously commonly used MD5-based hashing scheme has become obsolete because it is too vulnerable to attack. Newly designed systems are now switched to the SHA-512 hashing scheme, corresponding to $6$
seen in the /etc/shadow file.
Both generation and verification of user password hash values can be implemented with the POSIX C library function named crypt
. This function is defined as follows:
char *crypt(const char *key, const char *salt) |
The input parameter key
points to the string containing the user's password, and salt
points to a string in the format $<id>$<salt>
indicating the hash algorithm and salt to be used. Most Linux distributions use the crypt
function provided by the glibc library. The following figure summarizes the augmented crypt
function in Glibc:
In an embedded Linux system integrating uClibc, uClibc provides support for the crypt
function. But the test found that it returned a null pointer for the correct \\(6\\)
The answer lies in the uClibc's implementation of the crypt
function. Find the corresponding C source code:
|
Aha! It turns out that it only does MD5 hashing by default, and the codes of SHA-256 and SHA-512 need their own conditional compilation macro definitions. This is easy to handle, just edit the configuration file of uClibc and open the latter two.
\n--- a/toolchain/uClibc/config-0.9.33.2/common |
Finally, take a look at the program that comes with uClibc to test the SHA-512 hash algorithm. It clearly lists the data structures defined by the test code, including the salt, the input password, and the expected output, as well as several test vectors:
\nstatic const struct |
It can be seen that the last test case defines the round value 10 ($6$rounds=10$roundstoolow
), while the output shows that the round is 1000 (rounds=1000
). This confirms that the crypt
function implementation of uClibc matches the augmented function of Glibc - in order to ensure security, if the input specified round is too small, crypt
will automatically set to the minimum round of 1000.
In early May 2022, Nozomi Networks, a company focused on providing security solutions for industrial and critical infrastructure environments, released a newly discovered uClibc security vulnerability CVE-2022-30295. This vulnerability exists in the Domain Name System (DNS) implementation of all versions of uClibc and its fork uClibc-ng (prior to version 1.0.41). Since the implementation uses predictable transaction IDs when making DNS requests, there is a risk of DNS cache poisoning attacks.
\nSpecifically, applications often call gethostbyname
library functions to resolve a network address for a given hostname. uClibc/uClibc-ng internally implements a __dns_lookup
function for the actual DNS domain name request and response processing. Taking the last version 0.9.33.2 of uClibc as an example, the screenshot below shows the problematic code in the function __dns_lookup
:
Referring to line 1308, at the first DNS request, the variable local_id
is initialized to the transaction ID value of the last DNS request (stored in a static variable last_id
). Line 1319 is the actual culprit, it simply updates the old local_id
value by incrementing it by 1. This new value is stored back into the variable last_id
, as shown on line 1322. Finally, on line 1334, the value of local_id
is copied into the structure variable h
, which represents the actual content of the DNS request header. This code works pretty much in all available versions of uClibc and uClibc-ng prior to version 1.0.41.
This implementation makes the transaction ID in the DNS request predictable, because the attacker can estimate the value of the transaction ID in the next request as long as he/she detects the current transaction ID. By exploiting this vulnerability, an attacker can disrupt/poison the host's DNS cache by crafting a DNS response containing the correct source port and winning the competition with the legitimate response returned by the DNS server, making the network data of the application in the host system be directed to a trap site set by the attacker.
\nThe maintainers of uClibc-ng responded quickly to the announcement of this security vulnerability. They submitted a fix in mid-May 2022, and released version 1.0.41 including this patch at the end of that month. For uClibc, since this C standard library has stopped releasing any new versions since 2012, it is currently in an unmaintained state, so system R&D engineers need to come up with their repair. The following uClibc patches are available for reference:
\ndiff --git a/libc/inet/resolv.c b/libc/inet/resolv.c |
This uClibc patch is a simplified version of the uClibc-ng official patch. Its core is to read a double-byte random number from the system /dev/urandom
file, and then use it to set the original local_id
, the transaction ID of the DNS request. /dev/urandom
is a special device file of the Linux system. It can be used as a non-blocking random number generator, which will reuse the data in the entropy pool to generate pseudo-random data.
Note that in the above patch, the function dnsrand_setup
must first check urand_fd
whether it is positive, and only open /dev/urandom
when it is not true. Otherwise, the file will be reopened every time the application does a DNS lookup, the system will quickly hit the maximum number of file descriptors allowed, and the system will crash because it cannot open any more files.
Finally, a comparison of an embedded system using uClibc before and after adding DNS security patches is given. The following are the DNS packets intercepted by two sniffers. In the first unpatched system, the transaction ID of the DNS request is incremented in sequence, which is an obvious security hole; the second is after the patch is added, the transaction ID of each DNS request is a random value, and the loophole has been filled.
\n
By chance, I came across a picoCTF RSA challenge called Sum-O-Primes. This problem is not difficult, you can do it by knowing the basics of the RSA algorithm. In addition, if you are familiar with the history of the evolution of the RSA algorithm, you can find a second ingenious fast solution.
\npicoCTF is a free computer security education program created by security and privacy experts at Carnegie Mellon University. It uses original content built on the CTF (Capture the Flag) framework to provide a variety of challenges. It provides participants with valuable opportunities to systematically learn cybersecurity knowledge and gain practical experience.
\nThe collection of practice questions for picoCTF is called picoGym. The general problem solution is to search or decipher a string in the format \"picoCTF{...}\" from the given information, that is, the flag to be captured. As shown in the figure below, picoGym currently contains 271 cybersecurity challenge exercises, covering general skills, cryptography, reverse engineering, forensics, and other fields.
\nThere are 50 cryptography-related challenges in picoGym, one of which is Sum-O-Primes. The task of this challenge is simple and explained as follows:
\n\n\nWe have so much faith in RSA we give you not just the product of the primes, but their sum as well!
\n\n
\n- gen.py
\n- output.txt
\n
That is, we not only give the product of the two prime numbers used by RSA but also tell you their sum. How are these given? You need to discover by yourself from the rest of the information. After clicking the two links and downloading the file, open the first Python file:
\n#!/usr/bin/python |
If you have basic Python programming skills and understand the principles of the RSA algorithm, you should be able to read the above program quickly. What it does is:
\nflag.txt
to read the content. Then use the hexlify
and int
functions to convert it to an integer and store the result in a variable FLAG
.get_prime
to generate two prime numbers, store their sum in x
and their product in n
. Then assign 65537 to e
and calculate the RSA private exponent d
.pow
functions to perform modular exponentiation, which implements RSA encryption to encrypt plaintext FLAG
into ciphertext c
.x
, n
, and c
.Open the second file, which is apparently the output of the first program in Python:
\nx = 154ee809a4dc337290e6a4996e0717dd938160d6abfb651736d9f5d524812a659b310ad1f221196ee8ab187fa746a1b488a4079cddfc5db08e78be0d96c83c01e9bb42420b40d6f0ad9f220633459a6dc058bb01c517386bfbd2d4811c9b08558b0e05534768581a74884758d15e15b4ef0dbd6a338bf1f52eed4f137957737d2 |
Once you understand the meaning of the question, you can make a judgment immediately —— if you can decrypt the ciphertext c
and retrieve the plaintext FLAG, you can get the original content of flag.txt
, that is, capture the flag.
RSA decryption requires a private key exponent d
. Referring to the steps of the RSA algorithm below, it is obvious that this demands integer factorization for large prime numbers p
and q
first.
From here, the challenge becomes a problem that, knowing the sum and product of two large prime numbers known, find these two large prime numbers. That is, to solve a system of quadratic linear equations
\n\\[\n\\left\\{\n\\begin{aligned}\np+q &=n \\\\ \np*q &=x\n\\end{aligned} \n\\right. \n\\]
\nUsing the knowledge of elementary mathematics, the above equations can be transformed into a quadratic equation \\[p^2 - x * p + n = 0\\]
\nObviously, \\(p\\) and \\(q\\) are its two roots. According to the quadratic formula
\n\\[(p,q)={\\frac {x}{2}}\\pm {\\sqrt {\\left({\\frac {x}{2}}\\right)^{2}-n}}\\]
\nWe can get \\(p\\) and \\(q\\). The rest of the work is easy. The code to compute \\(d\\) from \\(p\\) and \\(q\\) can be copied directly from lines 28, 30, and 31 in gen.py. The final complete Python problem-solving code is as follows:
\nimport math |
The above program defines a general function solve_rsa_primes
to solve two large prime numbers. After it gets d
, the same pow
function is called to decrypt, and finally the plaintext is converted from a large integer to a byte sequence and printed out. The result of running this program is
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}' |
BINGO! Capture the Flag successfully!
\nNote: The function solve_rsa_primes
calls math.isqrt
to compute the integer square root of the given integer. This is indispensable! If it is written incorrectly with math.sqrt
, the following overflow error will occur
>>> |
This error happens because math.sqrt
uses floating-point arithmetic but fails to convert large integers to floating-point numbers.
The conventional solution to this problem has to solve a quadratic equation, so the integer square root operation is essential. Is there a solution that doesn't need a square root operation? The answer is yes.
\nIn the original RSA paper, the public exponent \\(e\\) and the private exponent \\(d\\) have the relationship as the following equation
\n\\[d⋅e≡1\\pmod{\\varphi(n)}\\]
\nHere the modular is the Euler's totient function \\(\\varphi(n)=(p-1)(q-1)\\). Since \\(\\varphi(N)\\) is always divisible by \\(\\lambda(n)\\), any d
satisfying the above also satisfies \\(d⋅e≡1\\pmod{\\lambda(n)}\\), thus the private exponent is not unique. Although the calculated \\(d>\\lambda(n)\\), the square root operation can be avoided when applied to the Sum-O-Primes problem. This is because \\[\n\\begin{aligned}\n\\varphi(n)&=(p-1)(q-1)\\\\\n&=pq-(p+q)+1\\\\\n&=n-x+1\n\\end{aligned}\n\\]
Hereby the formula for computing the private exponent becomes
\n\\[\n\\begin{aligned}\nd&≡e^{-1}\\pmod{\\varphi(n)}\\\\\n&≡e^{-1}\\pmod{(n-x+1)}\n\\end{aligned}\n\\]
\nNow that \\(n\\) and \\(x\\) are readily available, this method does not require finding \\(p\\) and \\(q\\) first, and naturally, there is no need for a square root operation. The Python code for this new solution is very concise
\nd1 = pow(e, -1, n - x + 1) |
To compare these two solutions, 4 lines of print and assert statements are added at the end. The execution result of this code is
\n>>> |
As shown above, this solution also succeeds in capturing the flag. The \\(d\\) value (d1
) calculated by the new solution is more than 7 times that of the conventional solution.
Click here to download all the code of this article: Sum-O-Primes.py.gz
\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming","CTF"]}] \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 7ebfc15..68d1b65 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -4,7 +4,7 @@