In a bitmap reader I found and modified, the first few colors of some images are innacurate by a large amount, in other images it reads perfectly, in just now, the first 7 or so colors of an image that I must have it read are not accurate at all. I don't understand byte orders, so please help!
Heres my modified copy of the code:
---
-- (Evil Steve)Because I'm a kind and wonderful person: http://www.gamedev.net/topic/572784-lua-read-bitmap/
---
function error(err)
-- Replace with your own error output method:
print(err);
end
-- Helper function: Parse a 16-bit WORD from the binary string
function ReadWORD(str, offset)
local loByte = str:byte(offset);
local hiByte = str:byte(offset+1);
return hiByte*256 + loByte;
end
-- Helper function: Parse a 32-bit DWORD from the binary string
function ReadDWORD(str, offset)
local loWord = ReadWORD(str, offset);
local hiWord = ReadWORD(str, offset+2);
return hiWord*65536 + loWord;
end
-- Process a bitmap file in a string, and call DrawPoint for each pixel
function OpenBitmap(File, Stream)
if Stream == nil then Stream = false end
local bytecode = File:read("*a")
-------------------------
-- Parse BITMAPFILEHEADER
-------------------------
local offset = 1;
local bfType = ReadWORD(bytecode, offset);
if(bfType ~= 0x4D42) then
error("Not a bitmap file (Invalid BMP magic value)");
return;
end
local bfOffBits = ReadWORD(bytecode, offset+10);
-------------------------
-- Parse BITMAPINFOHEADER
-------------------------
offset = 15; -- BITMAPFILEHEADER is 14 bytes long
local biWidth = ReadDWORD(bytecode, offset+4);
local biHeight = ReadDWORD(bytecode, offset+8);
local biBitCount = ReadWORD(bytecode, offset+14);
local biCompression = ReadDWORD(bytecode, offset+16);
if(biBitCount ~= 24) then
error("Only 24-bit bitmaps supported (Is " .. biBitCount .. "bpp)");
return;
end
if(biCompression ~= 0) then
error("Only uncompressed bitmaps supported (Compression type is " .. biCompression .. ")");
return;
end
---------------------
-- Parse bitmap image
---------------------
local TmpImg = {}
if Stream == false then
for y = biHeight-1, 0, -1 do
offset = bfOffBits + (biWidth*biBitCount/8)*y + 1;
for x = 0, biWidth-1 do
local b = bytecode:byte(offset);
local g = bytecode:byte(offset+1);
local r = bytecode:byte(offset+2);
offset = offset + 3;
TmpImg[#TmpImg+1] = {r,g,b}
end
end
else
for y = biHeight-1, 0, -1 do
offset = bfOffBits + (biWidth*biBitCount/8)*y + 1;
for x = 0, biWidth-1 do
local b = bytecode:byte(offset);
local g = bytecode:byte(offset+1);
local r = bytecode:byte(offset+2);
offset = offset + 3;
TmpImg[#TmpImg+1] = r
TmpImg[#TmpImg+1] = g
TmpImg[#TmpImg+1] = b
end
end
end
return TmpImg, biWidth, biHeight
end
function OpenBmp(FileName, Stream)
if Stream == nil then Stream = false end
if FileName == nil then
return false
end
local File = assert(io.open(FileName, 'rb'))
local Data, Width, Height = OpenBitmap(File, Stream)
File:close()
return Data, Width, Height
end
I cannot give you the code I run with this, sadly, because it has too many dependencies to bother with, but its output is:
<254, 254, 254, 256>
<99, 254, 254, 256>
<49, 74, 91, 256>
When ran with the following bmp colors:
<90, 106, 113, 256>
<188, 194, 197, 256>
<254, 254, 254, 256>
I don't see any pattern, and the bmp reader seems to make sense, it prints no errors when reading, and I made sure to save the bmp as 24 bit as required. all help appreciated :-)
In the example above, offset wasn't considering that row widths must always be a multiple of 4 bytes wide, with it padded if it was below the multiple of 4. You can resolve this by rounding up row width to the nearest multiple of 4, which explains why the function sometimes read images accurately, and other times not.
the behavior from the pixels in the beginning being false, but the rest later on being accurate was due to creeping, logically, the first would be accurate, with the last inaccurate, but the creeping went the opposite way due to bitmaps being read bottom-up and right-left.
Related
I’m working on my first GUI in Matlab. It’s gonna get kinda big but I’m starting very basic. So far all I’ve got is a button and axes.
I’m looping a matrix that is being updated every time it goes through the loop. I’d like to display this matrix in my GUI.
When I take out “axes(handles.axes1)” my GUI shuts down and a new window opens with exactly the picture I want on my GUI. When I leave it in all I get is Errors:
Reference to a cleared variable handles.
Error in RackWriter>onOff_Callback (line 141)
axes(handles.axes1)
Error in gui_mainfcn (line 95)
feval(varargin{:});
Error in RackWriter (line 42)
gui_mainfcn(gui_State, varargin{:});
Error in
#(hObject,eventdata)RackWriter('onOff_Callback',hObject,eventdata,guidata(hObject))
Error while evaluating DestroyedObject Callback
Anyone knows what I’m doing wrong?
Thanks so much in advance
Here’s how the matrix is created and how i was planning on showing it:
% Reshape data (1D -> 2D array)
data2d = zeros(nrow, ncol);
k = 1;
for i = 1:nrow
for j = 1:ncol
data2d(row_index(i), col_index(j)) = data(k);
k = k + 1;
end
end
%resize 16x10 image to 160x100 image
data2d_resized = imresize(data2d,10);
%sensetivity
axes(handles.axes1)
imshow(data2d_resized,[0 255]);
This should do the trick:
handles.figure = imshow(data2d_resized, [0 255], 'parent', handles.axes1);
If you want to update your figure in a later stage, you can then use:
set(handles.figure, 'CData', updated_matrix);
Also, make sure to put the next line after each function in you code, it updates the handles:
guidata(hObject,handles);
function varargout = RackWriter(varargin)
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', #RackWriter_OpeningFcn, ...
'gui_OutputFcn', #RackWriter_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
% --- Executes just before RackWriter is made visible.
function RackWriter_OpeningFcn(hObject, eventdata, handles, varargin)
% Choose default command line output for RackWriter
handles.output = hObject;
% Update handles structure
guidata(hObject, handles);
axes(handles.axes2)
imshow('sensordeckelOben.jpg');
% UIWAIT makes RackWriter wait for user response (see UIRESUME)
% uiwait(handles.figure1);
% --- Outputs from this function are returned to the command line.
function varargout = RackWriter_OutputFcn(hObject, eventdata, handles)
% Get default command line output from handles structure
varargout{1} = handles.output;
% --- Executes on button press in onOff.
function onOff_Callback(hObject, eventdata, handles)
% hObject handle to onOff (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
%This is where my stuff begins
% Preparations
close all %close all figures
clear all %clear all workspace variables
fclose('all') %close all Files
delete(instrfindall) %Reset Com Port
delete(timerfindall) %Delete Timers
%clear handles
% setup serial
serialPort = serial('COM3');
command = 'A';
nrow = 16;
ncol = 10;
row_index = [9,10,11,12,13,14,15,16,8,7,6,5,4,3,2,1];
col_index = [1,2,3,4,5,6,7,8,9,10];
% 10x16 = 160 bytes
lendata = 160;
BaudRate = 115200;
%InputBufferSize is bein displayed (disp(serialPort.BytesAvailable))
%with only 322 Bytes. The more information it has to process, the more
%bytes that havve to be stored in the InputBufferSize. But it seams to
%not go over 400
InputBufferSize = 500;
Timeout = 1;
set(serialPort , 'BaudRate', BaudRate);
set(serialPort , 'InputBufferSize', InputBufferSize);
set(serialPort , 'Timeout', Timeout);
fopen(serialPort);
while 1
% Request data
fprintf(serialPort, command);
% Get data
%Data is read as string (CSV)
data_string = fgetl(serialPort);
data_string_array = strsplit(data_string, ',');
data = str2double(data_string_array);
% Reshape data (1D -> 2D array)
data2d = zeros(nrow, ncol);
k = 1;
for i = 1:nrow
for j = 1:ncol
data2d(row_index(i), col_index(j)) = data(k);
k = k + 1;
end
end
%resize 16x10 image to 160x100 image
data2d_resized = imresize(data2d,10);
%sensetivity [0 255]
%axes(handles.axes1)
imshow(data2d_resized,[0 50]);
%clean out the InputBufferSize
flushinput(serialPort)
end
fclose(serialPort);
I am creating a function that will parse and ICO/CUR and convert the data into plain pixels (specific to my API) that will then be fed to a dxCreateTexture function which will create the final image. I'm currently working on the case when the images inside the ICO file are 8bpp or less. Here's how it's currently done:
I read the color palette and store each color inside an array.
I move on to reading the XOR mask which contains the indices for every pixel color and store every pixel inside another table.
I then read the AND mask which I understand is 1bpp.
The code that I will post below works perfectly for 1bpp, 4bpp and 8bpp images with a size of 32x32, XOR & AND masks being interpreted correctly, but for images with 8x8, 16x16 or 48x48 sizes (and I suspect that there are other sizes too) only the XOR mask gets interpreted correctly. Reading the AND mask will result in misplaced transparent pixels. Please keep in mind that I'm not flipping the image yet, so this code will result in an upside-down image.
local IcoSignature = string.char(0,0,1,0);
local PngSignature = string.char(137,80,78,71,13,10,26,10);
local AlphaByte = string.char(255);
local TransparentPixel = string.char(0,0,0,0);
function ParseCur(FilePath)
if (fileExists(FilePath) == true) then
local File = fileOpen(FilePath);
if (File ~= false) and (fileRead(File,4) == IcoSignature) then
local Icons = {}
for i = 1,fileReadInteger(File,2) do -- number of icons in file
local SizeX = fileReadInteger(File,1); -- icon width
if (SizeX == 0) then
SizeX = 256;
end
local SizeY = fileReadInteger(File,1); -- icon height
if (SizeY == 0) then
SizeY = 256;
end
fileRead(File,2); -- skip ColorCount and Reserved
local PlanesNumber = fileReadInteger(File,2);
local BitsPerPixel = fileReadInteger(File,2);
local Size = fileReadInteger(File); -- bytes occupied by icon
local Offset = fileReadInteger(File); -- icon data offset
Icons[i] = {
PlanesNumber = PlanesNumber,
BitsPerPixel = BitsPerPixel,
SizeX = SizeX,
SizeY = SizeY,
Texture = true
}
local PreviousPosition = fileGetPos(File);
fileSetPos(File,Offset);
if (fileRead(File,8) == PngSignature) then -- check data format (png or bmp)
fileSetPos(File,Offset);
-- to do
else
fileSetPos(File,Offset+4); -- skip BITMAPINFOHEADER Size
local SizeX = fileReadInteger(File);
local SizeY = fileReadInteger(File)/2;
local PlanesNumber = fileReadInteger(File,2);
local BitsPerPixel = fileReadInteger(File,2);
fileRead(File,24); -- skip rest of BITMAPINFOHEADER
local Pixels = {}
if (BitsPerPixel == 1) or (BitsPerPixel == 4) or (BitsPerPixel == 8) then
local Colors = {}
for j = 1,2^(PlanesNumber*BitsPerPixel) do
Colors[j] = fileRead(File,3)..AlphaByte;
fileRead(File,1);
end
local PixelsPerByte = 8/BitsPerPixel;
local CurrentByte;
for y = 1,SizeY do -- XOR mask
Pixels[y] = {}
local CurrentRow = Pixels[y];
for x = 0,SizeX-1 do
local CurrentBit = x%PixelsPerByte;
if (CurrentBit == 0) then
CurrentByte = fileReadInteger(File,1);
end
CurrentRow[x+1] = Colors[bitExtract(
CurrentByte,
(PixelsPerByte-1-CurrentBit)*BitsPerPixel,BitsPerPixel
)+1];
end
end
for y = 1,SizeY do -- AND mask
local CurrentRow = Pixels[y];
for x = 0,SizeX-1 do
local CurrentBit = x%8;
if (CurrentBit == 0) then
CurrentByte = fileReadInteger(File,1);
end
if (bitExtract(CurrentByte,7-CurrentBit,1) == 1) then
CurrentRow[x+1] = TransparentPixel;
end
end
end
for y = 1,SizeY do -- concatenate rows into strings
Pixels[y] = table.concat(Pixels[y]);
end
Icons[i].Texture = dxCreateTexture(
table.concat(Pixels)..string.char(
bitExtract(SizeX,0,8),bitExtract(SizeX,8,8),
bitExtract(SizeY,0,8),bitExtract(SizeY,8,8)
), -- plain pixels
nil,
false
);
elseif (BitsPerPixel == 16) or (BitsPerPixel == 24) or (BitsPerPixel == 32) then
-- to do
end
end
fileSetPos(File,PreviousPosition); -- continue reading next ICO header
end
fileClose(File);
return Icons;
end
end
end
I suppose that fileExists, fileOpen, fileClose, fileGetPos and fileSetPos are self-explanatory functions. The rest of the functions' arguments are as follows:
fileRead(file file, number bytes) - reads bytes bytes from file and returns them as a string
fileReadInteger(file file, [number bytes = 4], [bool order = true]) - reads an integer with the size of bytes bytes from file in order (little -edian = true, big-edian = false)
bitExtract(number value, number filed, number width)
dxCreateTexture(string pixels, [string format = "argb"], [bool mipmaps = true])
Here are some outputs of the function in its current state: http://i.imgur.com/dRlaoan.png
The first image is 16x16 with AND mask code commented out, second is 32x32 with AND mask code commented out, third is 16x16 with AND mask code and fourth is 32x32 with AND mask code. 8x8 and 48x48 images with AND mask code look the same as the third image in the demonstration.
ICO used for demonstration: http://lua-users.org/files/wiki_insecure/lua-std.ico
Thank you, #EgorSkriptunoff!
This mask is also a subject to right-padding its every line with zeroes.
This was indeed the problem. Three lines of code inside each loop solved it.
XOR mask:
if ((SizeX/PixelsPerByte)%4 ~= 0) then
fileRead(File,4-SizeX/PixelsPerByte%4);
end
AND mask:
if ((SizeX/8)%4 ~= 0) then
fileRead(File,4-SizeX/8%4);
end
I work in a neuro-lab that records pictures of mouse brains. The raw files that the cameras record to record pictures from alternating cameras (picture of brain, picture of mouse head, picture of brain, etc) We're converting these files to TIFF, deinterleaving them, and then concatenating the files.
The concatenation is far too slow to be of any use. I'm not yet learned enough in Matlab to be able to troubleshoot. How can we improve the speed of this code?
%convert micam raw to tiff using image j
javaaddpath 'C:\Program Files\MATLAB\R2013a\java\mij.jar';
javaaddpath 'C:\Program Files\MATLAB\R2013a\java\ij.jar';
MIJ.start('C:\users\lee\desktop\imagej');
MIJ.run('Install...', 'install=[C:\\Users\\lee\\Desktop\\ImageJ\\macros\\Matthias\\Helmchen Macros modified djm.ijm]');
MIJ.run('Run...', 'path=[C:\\Users\\lee\\Desktop\\ImageJ\\macros\\Matthias\\Helmchen Macros modified djm.ijm]');
MIJ.run('Convert MiCam Raw to TIFF');
pause (30); %to prevent race condition, needs to be fixed to wait for input
MIJ.exit;
%prepare tiff stack folder fast
myFolder = uigetdir;
cd(myFolder);
filePattern = fullfile(myFolder, '*.tif');
tifffiles = dir(filePattern);
count = length(tifffiles);
for z = 1:count
A = tifffiles.name;
I = imreadtiffstack (A, 256);
%crop tiff stack
sizecrop = size(I);
framenum = sizecrop(3);
cropcollector = ones([100 101 256] , 'uint16'); %preallocates matrix for depositing cropped frames
for k = 1:framenum
frame = I(:,:,k);
I2 = imcrop(frame,[20 0 100 100]);
cropcollector(:,:,k)=I2;
%deinterleaves tiff stack
sizedinlv = size(cropcollector);
framenumdinlv = sizedinlv(3);
oddcollector = ones([100 101 128], 'uint16'); %preallocates array for odd deinterleaved frames
evencollector = ones([100 101 128], 'uint16'); %preallocates array for even deinterleaved frames
countodd = 0;
counteven = 0;
for k2 = (1:framenumdinlv)
if mod(k2, 2)==1
framedinlv = cropcollector(:,:,k2);
countodd = countodd +1;
oddcollector(:,:,countodd)=framedinlv;
else
framedinlv = cropcollector(:,:,k2);
counteven = counteven + 1;
evencollector(:,:,counteven)=framedinlv;
%concatenate
if mod (z, 2)==1;
oddhold = ones([100 101 128], 'uint16');
evenhold = ones([100 101 128], 'uint16');
oddhold = repmat(oddcollector, 1);
evenhold = repmat(evencollector, 1);
else
odd = num2str(1);
even = num2str(2);
brain = ones([100 101 256], 'uint16');
mouse = ones([100 101 256], 'uint16');
% nameoddframes = strcat(A(1:10), odd);
%nameevenframes = strcat(A(1:10), even);
brain = cat(3, oddhold, oddcollector);
mouse = cat(3, evenhold, evencollector);
end
end
end
end
end
%background subtraction
I am using Excel 2007 which supports Columns upto 16,384 Columns. I would like to obtain the Column name corresponding Column Number.
Currently, I am using the following code. However this code supports upto 256 Columns. Any idea how to obtain Column Name if the column number is greater than 256.
function loc = xlcolumn(column)
if isnumeric(column)
if column>256
error('Excel is limited to 256 columns! Enter an integer number <256');
end
letters = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
count = 0;
if column-26<=0
loc = char(letters(column));
else
while column-26>0
count = count + 1;
column = column - 26;
end
loc = [char(letters(count)) char(letters(column))];
end
else
letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
if size(column,2)==1
loc =findstr(column,letters);
elseif size(column,2)==2
loc1 =findstr(column(1),letters);
loc2 =findstr(column(2),letters);
loc = (26 + 26*loc1)-(26-loc2);
end
end
Thanks
As a diversion, here is an all function handle example, with (almost) no file-based functions required. This is based on the dec2base function, since Excel column names are (almost) base 26 numbers, with the frustrating difference that there are no "0" characters.
Note: this is probably a terrible idea overall, but it works. Better solutions are probably found elsewhere in the file exchange.
First, the one file based function that I couldn't get around, to perform arbitrary depth function composition.
function result = compose( fnHandles )
%COMPOSE Compose a set of functions
% COMPOSE({fnHandles}) returns a function handle consisting of the
% composition of the cell array of input function handles.
%
% For example, if F, G, and H are function handles with one input and
% one output, then:
% FNCOMPOSED = COMPOSE({F,G,H});
% y = FNCOMPOSED(x);
% is equivalent to
% y = F(G(H(x)));
if isempty(fnHandles)
result = #(x)x;
elseif length(fnHandles)==1
result = fnHandles{1};
else
fnOuter = fnHandles{1};
fnRemainder = compose(fnHandles(2:end));
result = #(x)fnOuter(fnRemainder(x));
end
Then, the bizarre, contrived path to convert base26 values into the correct string
%Functions leading to "getNumeric", which creates a numeric, base26 array
remapUpper = #(rawBase)(rawBase + (rawBase>='A')*(-55)); %Map the letters 'A-P' to [10:26]
reMapLower = #(rawBase)(rawBase + (rawBase<'A')*(-48)); %Map characters '0123456789' to [0:9]
getRawBase = #(x)dec2base(x, 26);
getNumeric = #(x)remapUpper(reMapLower(getRawBase(x)));
%Functions leading to "correctNumeric"
% This replaces zeros with 26, and reduces the high values entry by 1.
% Similar to "borrowing" as we learned in longhand subtraction
borrowDownFrom = #(x, fromIndex) [x(1:(fromIndex-1)) (x(fromIndex)-1) (x(fromIndex+1)+26) (x((fromIndex+2):end))];
borrowToIfNeeded = #(x, toIndex) (x(toIndex)<=0)*borrowDownFrom(x,toIndex-1) + (x(toIndex)>0)*(x); %Ugly numeric switch
getAllConditionalBorrowFunctions = #(numeric)arrayfun(#(index)#(numeric)borrowToIfNeeded(numeric, index),(2:length(numeric)),'uniformoutput',false);
getComposedBorrowFunction = #(x)compose(getAllConditionalBorrowFunctions(x));
correctNumeric = #(x)feval(getComposedBorrowFunction(x),x);
%Function to replace numerics with letters, and remove leading '#' (leading
%zeros)
numeric2alpha = #(x)regexprep(char(x+'A'-1),'^#','');
%Compose complete function
num2ExcelName = #(x)arrayfun(#(x)numeric2alpha(correctNumeric(getNumeric(x))), x, 'uniformoutput',false)';
Now test using some stressing transitions:
>> num2ExcelName([1:5 23:28 700:704 727:729 1024:1026 1351:1355 16382:16384])
ans =
'A'
'B'
'C'
'D'
'E'
'W'
'X'
'Y'
'Z'
'AA'
'AB'
'ZX'
'ZY'
'ZZ'
'AAA'
'AAB'
'AAY'
'AAZ'
'ABA'
'AMJ'
'AMK'
'AML'
'AYY'
'AYZ'
'AZA'
'AZB'
'AZC'
'XFB'
'XFC'
'XFD'
This function I wrote works for any number of columns (until Excel runs out of columns). It just requires a column number input (e.g. 16368 will return a string 'XEN').
If the application of this concept is different than my function, it's important to note that a column of x number of A's begins every 26^(x-1) + 26^(x-2) + ... + 26^2 + 26 + 1. (e.g. 'AAA' begins on 26^2 + 26 + 1 = 703)
function [col_str] = let_loc(num_loc)
test = 2;
old = 0;
x = 0;
while test >= 1
old = 26^x + old;
test = num_loc/old;
x = x + 1;
end
num_letters = x - 1;
str_array = zeros(1,num_letters);
for i = 1:num_letters
loc = floor(num_loc/(26^(num_letters-i)));
num_loc = num_loc - (loc*26^(num_letters-i));
str_array(i) = char(65 + (loc - 1));
end
col_str = strcat(str_array(1:length(str_array)));
end
Hope this saves someone some time!
It seems that there're 6 variations to CBC-MAC algorithm. I've been trying to match the MAC algorithm on the PINPad 1000SE [which per manual is ISO 9797-1 Algorithm 1].
I got an excellent start from here.
And I coded the algorithm as below:
public static byte[] CalculateMAC(this IPinPad pinpad, byte[] message, byte[] key)
{
//Divide the key with Key1[ first 64 bits] and key2 [last 64 bits]
var key1 = new byte[8];
Array.Copy(key, 0, key1, 0, 8);
var key2 = new byte[8];
Array.Copy(key, 8, key2, 0, 8); //64 bits
//divide the message into 8 bytes blocks
//pad the last block with "80" and "00","00","00" until it reaches 8 bytes
//if the message already can be divided by 8, then add
//another block "80 00 00 00 00 00 00 00"
Action<byte[], int> prepArray = (bArr, offset) =>
{
bArr[offset] = 0; //80
for (var i = offset + 1; i < bArr.Length; i++)
bArr[i] = 0;
};
var length = message.Length;
var mod = length > 8? length % 8: length - 8;
var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 0);
//var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 8);
Debug.Assert(newLength % 8 == 0);
var arr = new byte[newLength];
Array.Copy(message, 0, arr, 0, length);
//Encoding.ASCII.GetBytes(message, 0, length, arr, 0);
prepArray(arr, length);
//use initial vector {0,0,0,0,0,0,0,0}
var vector = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
//encrypt by DES CBC algorith with the first key KEY 1
var des = new DESCryptoServiceProvider { Mode = CipherMode.CBC };
var cryptor = des.CreateEncryptor(key1, vector);
var outputBuffer = new byte[arr.Length];
cryptor.TransformBlock(arr, 0, arr.Length, outputBuffer, 0);
//Decrypt the result by DES ECB with the second key KEY2 [Original suggestion]
//Now I'm Encrypting
var decOutputBuffer = new byte[outputBuffer.Length];
des.Mode = CipherMode.ECB;
var decryptor = des.CreateEncryptor(key2, vector);
//var decryptor = des.CreateDecryptor(key2, vector);
decryptor.TransformBlock(outputBuffer, 0, outputBuffer.Length, decOutputBuffer, 0);
//Encrypt the result by DES ECB with the first key KEY1
var finalOutputBuffer = new byte[decOutputBuffer.Length];
var cryptor2 = des.CreateEncryptor(key1, vector);
cryptor2.TransformBlock(decOutputBuffer, 0, decOutputBuffer.Length, finalOutputBuffer, 0);
//take the first 4 bytes as the MAC
var rval = new byte[4];
Array.Copy(finalOutputBuffer, 0, rval, 0, 4);
return rval;
}
Then I discovered there're 3 padding schemes and the one that gave me a start may not necessarily be right. The manual came to my rescue again. It seems the device only pads with 0s. Additional block is also nowhere mentioned so I made the below changes:
Action<byte[], int> prepArray = (bArr, offset) =>
{
bArr[offset] = 0; ... }
No additional block (if mod 0 [divisible by 8] do not change array length)
var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 0);
The original suggestion wanted me to decrypt at the second step... but Valery here suggests that it's encrypt all the way. So I changed Decrypt to Encrypt. But still I'm unable to get the requisite MAC...
Manual says for key "6AC292FAA1315B4D8234B3A3D7D5933A" [since the key should be 16 bytes, I figured the key here's hex string so I took byte values of 6A, C2, 92, FA...
new byte[] { 106, 194, 146, ...] the MAC should be 7B,40,BA,95 [4 bytes] if the message is [0x1a + byte array of MENTERODOMETER]
Can someone help? Please?
Since Pinpad requires that the first character in message is a 0x1a...
public static byte[] CalculateAugmentedMAC(this IPinPad pinpad, string message, byte[] key)
{
var arr = new byte[message.Length + 1];
var source = Encoding.ASCII.GetBytes(message);
arr[0] = 0x1a; //ClearScreenIndicator
Array.Copy(source, 0, arr, 1, source.Length);
return CalculateMAC(pinpad, arr, key);
}
I'm calling the code above with this input:
var result = pad.CalculateAugmentedMAC("MENTERODOMETER", new byte[] { 106, 194, 146, 250, 161, 49, 91, 77, 130, 52, 179, 163, 215, 213, 147, 58 });
Most CBC MAC algorithms are implemented in BouncyCastle's JCE provider.
Look at: BouncyCastleProvider.java
You're probably looking for DESEDEISO9797ALG1MACWITHISO7816-4PADDING, which is an alias for DESEDEMAC64WITHISO7816-4PADDING, implemented here (well, it's a specific configuration of CBCBlockCipherMac using the DESedeEngine and ISO7816d4Padding, you'll have to jump between some classes to get the full picture):
JCEMac.java
Also, have a look at jPos:
JCESecurityModule.java
and their contributed retail MAC algorithm implementation:
retail-mac-contributed-by-vsalaman.zip
I am pretty sure (IIRC) that you need to call TransformFinalBlock at the end (per encryptor).
Can't answer to your specific terminal, but I use this to test MACs.
public static byte[] GenerateMAC(byte[] key, byte[] data)
{
using (MACTripleDES mac = new MACTripleDES(key))
return mac.ComputeHash(data);
}