Advertisements

Top Ways and Time for Hackers to Brute Force Your Passwords – 2020

Last Updated: 7th September, 2020

Time for Hackers to Brute Force Your Passwords

Time-it-takes-for-hackers-to-brute-force-your-password

Ever wondered how long will it take for hackers to break into your passwords? The Infographic above shows exacty the time vs complexity of your passwords. Yellow zone passwords are secured and can’t be cracked in an amount of time that isn’t feasible.

How does Passwords Work?

All passwords are first hashed before being stored. A hash is a one way mathematical function that transforms an input into an output. It has the property that the same input will always result in the same output. Modern hashing algorithms are very difficult to break, so one feasible way to discover a password is to perform a brute force attack on the hash.

There are a few factors used to compute how long a given password will take to brute force. To compute the time it will take, you must know the length of the password, the character set used, and how many hashes can be checked every second.

On a modern computer (8 core, 2.8 GHz) using the SHA512 hashing algorithm, it takes about 0.0017 milliseconds to compute a hash. This translates to about 1.7*10^-6 seconds per password, or 588235 passwords per second. Although we will not use the metric in this article, it is important to note that a GPU, or 3D card, can calculate hashes at a speed 50-100 times greater than a computer. For the purposes of this KB article, we will calculate how long given passwords can be cracked using a single modern computer. We also calculate how long they can be cracked using a supercomputer, which is approximately equivalent to a botnet with 100000 computers. Modern supercomputers can be up to 150000 faster than their desktop counterparts and a 100000 computer botnet is feasible; the largest botnet to date is estimated to have 12 million computers. We also assume that on average, the password will be cracked when half of the possible passwords are checked.

As you can see, simply using lowercase and uppercase characters is not enough. If we include numbers, such as in the password “r3Dcr0W5”, there are 62 characters in the set. To break this password, it will take (1.7*10^-6 * 62^8) seconds / 2, or 5.88 years. Although this is infeasible on a single desktop computer, it would still only take 31 minutes to break on a botnet. Even if you increase this to 10 characters, it can be broken in 83 days on a supercomputer or botnet. If that botnet utilizes the GPU for all computers, it can potentially be broken in less than a day.

If you include symbols, then depending on the symbols used, there are about 80 characters in the set. To break a password such as “%ZBGbv]8”, it would take (1.7*10^-6 * 80^8) seconds / 2, or 45.2 years. On a supercomputer or botnet, this will take 4 hours.

So, even if you use a very secure set of characters, your password should be at least 10 characters long. To break a 10 character password that uses letters, numbers, and symbols, such as “%ZBGbv]8g?”, it would take (1.7*10^-6 * 80^10) seconds / 2 or 289217 years. This would take about 3 years on a supercomputer or botnet.

The moral of the story is that passwords should be at least 10 characters long and include a mix of numbers, lowercase letters, uppercase letters and symbols.

Top ways Hackers Brute Force your password

1. Brute Force

Risk Level: Low

Surprisingly not as prevalent as people tend to think, brute forcing passwords is difficult, time-consuming and expensive for criminals.

What Is It?

It’s the kind of thing that security researchers like to write about, or which you might see in TV shows: a hacker runs an algorithm against an encrypted password and in 3…2…1… the algorithm cracks the password and reveals it in plain text.

How Does It Work?

There are plenty of tools like “Aircrack-ng”, “John The Ripper”, and “DaveGrohl” that attempt to brute force passwords. There’s generally two kinds of cracking available. The first is some form of “dictionary” attack – so called because the attacker just tries every word in the dictionary as the password. Programs like those mentioned above can run through and test an entire dictionary in a matter of seconds.

How Can You Stay Safe?

The key to staying safe from brute force attacks is to ensure you use passwords of sufficient length. Anything 16 characters or over should be sufficient given current technology, but ideally future-proof yourself by using a passphrase that is as long as the maximum allowed by the service that you’re signing up to. Avoid using any service that doesn’t let you create a password longer than 8 or 10 characters. Worried about how you’d remember a super long password? 

2. Phishing

Risk Level: High

Over 80% of all cyber crimes begin with a Phishing.

 Hackers love to use phishing techniques to steal user credentials, either for their own use, or more commonly to sell to criminals on the dark net.

What Is It?

Phishing is a social engineering trick which attempts to trick users into supplying their credentials to what they believe is a genuine request from a legitimate site or vendor.

How Can You Stay Safe?

Use 2-factor or multi-factor authentication. Although researchers have developed tricks to overcome these, in the wild cases are yet to be reported. Caution is your number one defense against phishing. Ignore requests to sign in to services from email links, and always go directly to the vendor’s site in your browser. Check emails that contain attachments carefully. The majority of phishing emails contain misspellings or other errors that are not difficult to find if you take a moment to inspect the message carefully.

Advertisements

Bank ATM migration to Windows 10 – Deadline Jan-2020

Last Updated: 7th September, 2020

Bank ATM migration to Windows 10 - Deadline Jan-2020

Bank ATM migration to Windows 10

The banking sector will face a big ATM migration challenge in 2020. Microsoft made the official announcement: Windows 7 (operating system for many ATMs) extended support will end on January 14, 2020. Consequently, all banks have to update their entire ATM network by installing a new operating system caring about data security.

The wave of sticker shock and nausea is growing as financial institutions are now receiving their Windows 10 pricing from ATM vendors.

Core upgrades, ATM rip-and-replace, service calls, OEM maintenance licenses and organizational disruption — big expense without a single dollar in additional revenue. Oh, and you’d better hurry up and commit to a Win10 upgrade now because lead times on those new Win10 ATMs is six months and growing.

With less than a month left before Windows 7 goes end-of-life, who can blame financial institutions for feeling like they are over the proverbial barrel — again.

There are about 3.2 million ATMs in the world. They are used daily by billions of people, but only a few know that most ATMs work on the Windows operating system.

A lot of ATMs around the globe are still running Windows XP embedded, long after Microsoft ceased support with security and stability patches. Support for Windows XP was discontinued in 2014, which means that since then the Microsoft Company has not rolled out any security updates for this Windows version.

BANK Expenditure or Investment?

It‘s time to get prepared for ATM migration to Windows 10. It is not a secret that many banks have delayed the decison. What is the reason? It seems to be due to extra costs for the bank, but does this need to be consider as expenditure or investment?

Undoubtedly, it must be seen as an investment. Not just an investment into new software, but into ATM network safety. That means investing into the bank’s reputation given what we know about criminal activity surrounding ATMS.

So, why is it beneficial to migrate to Windows 10?

  1. Greater security is ensured. This operating system is compatible with the latest PCI and VISA safety standards. The Secure Boot requires that any program that begins as the operating system startup needs to be signed off by both Microsoft and the hardware manufacturer. Security additions with Windows 10 make the Secure Boot feature tamper-proof.
  2. It is the most suitable platform for making ATMs work as an extra distribution or sales channel.
  3. Windows 10 reduces maintenance costs by getting the latest software and security updates. ATMs with outdated software support can be risky and expensive.
  4. Long-term investment. Microsoft is about to support Windows 10 operating system to 2026. This will allow financial organisations to plan their expenses for ATM software.

ATMs migration to Windows 10 is a long and complicated process. Financial experts advise not to postpone it to the last minute and not to risk safety and reputation, which can be compromised by using an obsolete unsafe operating system.

All about SAP HANA (Infographics)

Last Updated: 7th September, 2020

Guest Post – Carloski R.

SAP HANA Editions

SAP-HANA-Infographic

What is SAP HANA

There is no doubt that SAP HANA is becoming the hottest technology platform in the market of IT. More than 1200 companies from 58 countries developing applications on this platform. If you have ever considered how SAP S/4 HANA works and how it helps the client to enhance their business, then continue to read this article.

SAP S/4HANA is SAP’s next generation business suite designed to help you run simple in a digital and networked world. 
This new suite is built on our advanced in-memory platform, SAP HANA, and offers a personalized user experience with SAP Fiori. 
Deployable in the cloud or on-premise, SAP S/4HANA is built to drive instant value across lines of business and industries with the ultimate in sophistication: simplicity. The repeat of this course features some updated information along with a new unit on SAP Activate. SAP Activate is the new innovation adoption accelerator introduced with SAP S/4HANA, a unique combination of SAP Best Practices, Methodology, and Guided Configuration delivered with a reference solution.

Differences between SAP HANA & SAP S/4HANA

Some levels of confusion between SAP HANA and SAP S/4HANA still exist with the majority of the people these days. In this article, we provide clarity for those who are wrestling with the differences between the two. It is important to understand their functionality and constraints to make use of the products efficiently. In order to understand the differences between SAP HANA and SAP S/4HANA, one must understand the basic concepts of SAP HANA and SAP S/4HANA.

SAP HANA is a database, an in-memory database, while SAP S/4HANA is an application which is designed to run on the SAP HANA database. It is a revolutionary platform-based in the company’s new In-memory database. Learning it will imply that choosing to pursue a career path that is both fulfilling and exciting to work with. SAP HANA acts as a hub for all SAP’s products strategy and it serves as the base for recent technology SAP S/4HANA that is set to serve as a cornerstone for all SAP technologies.

What SAP HANA is all about

HANA is the backend that runs the SAP landscape. Its central feature is an innovative, column-based Relational Database Management System (RDBMS), which is used to store, retrieve and process data on core business activities. SAP HANA itself doesn’t determine what sorts of tasks a business does, it can accommodate any type of data. Businesses install applications that run on top of HANA, such as SAP applications for finance, HR, and logistics. As such, companies have to make choices about what software best meets their current needs.

Unlike other RDBMSs SAP HANA reduces the memory usage factor by 10 and improving performance as it uses column oriented storage which combines OLAP and OLTP into a single structure. The speed of both Online Transaction Processing (OLTP) and Online Analytical Processing (OLAP) can be drastically changed with the design of SAP HANA. Information of the majority databases is stored on the hard drive which in result keeps an only limited amount of information in main memory. Hard drives are relatively slow, which limits how fast they can recall information.

SAP HANA is made up of a simpler structure and lower memory footprint than other RDBMSs. A system like OLAP and OLTP are stored in different databases which result in insufficient memory, redundant information bloating the DB footprint.

Hence, SAP HANA can do real-time analytics, crunching data nearly instantaneously. This allows businesses to react more quickly to changing conditions, providing significant strategic benefits.

SAP HANA isn’t just a new choice for enterprise computing; because it handles data very differently from other databases, it is designed to run SAP software. SAP SE has been reworking their core ERP applications to better harness HANA’s speed and flexibility, and will only support older versions of the software until 2025, at which point customers need to have completed their SAP HANA migration, and upgraded to the new software.

What is SAP S/4HANA all about?

SAP S/4HANA is the shorter form of SAP Business Suite 4 SAP HANA, which means it is the fourth version of SAP Business Suite. It is designed to run only on SAP HANA. The transition of SAP users to SAP S/4HANA is similar to the earlier transition from the ERP versions, SAP R/2 to SAP R/3.

The next generation Business Suite of SAP is SAP S/4HANA which is designed in a simplified way specifically to work with SAP HANA and to replace the SAP ECC/ERP.

SAP S/4HANA is the in-memory version of the Business Suite ERP platform.  SAP S/4HANA was announced in February 2015 and billed as SAP’s “most important release in 23 years”, S/4HANA is intended to be easier to use and administer by helping to solve more complex problems and handle vastly larger amounts of data than its predecessors. S/4HANA is available in on-premises, cloud and hybrid deployment models.

As per the SAP, developers feel the changes in SAP as they find ERP system is more agile, simpler to understand and use. This change is termed as the opportunity for businesses to reinvent business models and re-generate revenues with the advantage of the Internet of Things (IoT) and big data by connecting people business networks and devices by the SAP.

As per the SAP, Batch processing is not required for S/4HANA this makes the businesses to simplify their processes and drive them in real time which mean that the business user can access insight on data from anywhere in real time for prediction, execution, Planning and simulation.

SAP Simple Finance is one of the main components of S/4HANA, which aims to streamline financial processes and enable real-time analysis of financial data. Simple Finance helps companies align their financial and non-financial data into what SAP refers to as a “single source of truth.” Some Business Suite users are deploying Simple Finance as the first step in the road to S/4HANA.

Conclusion

The popularity of SAP HANA and SAP S/4HANA has led to widespread usage across the globe. The demand for these modules is very high and a smart professional must leverage this trend in order to take advantage of the market demands.

Book a Demo? Please visit: http://affirmts.com

Python Programming – Beginners Guide 2020

Last Updated: 13th January, 2020

Python Beginners Guide

This tutorial will attempt to teach you Python in 10 minutes. It’s probably not so much a tutorial as it is a cross between a tutorial and a cheatsheet, so it will just show you some basic concepts to start you off. Obviously, if you want to really learn a language you need to program in it for a while. I will assume that you are already familiar with programming and will, therefore, skip most of the non-language-specific stuff. The important keywords will be highlighted so you can easily spot them. Also, pay attention because, due to the terseness of this tutorial, some things will be introduced directly in code and only briefly commented on.

We will focus on Python 3, as that is the version you should use. All the examples in the book are in Python 3, and if anyone advises you to use 2, they aren’t your friend.

Properties

Python is strongly typed (i.e. types are enforced), dynamically, implicitly typed (i.e. you don’t have to declare variables), case sensitive (i.e. var and VAR are two different variables) and object-oriented (i.e. everything is an object).

Getting help

Help in Python is always available right in the interpreter. If you want to know how an object works, all you have to do is call help(<object>)! Also useful are dir(), which shows you all the object’s methods, and <object>.__doc__, which shows you its documentation string:

>>> help(5)
Help on int object:
(etc etc)

>>> dir(5)
['__abs__', '__add__', ...]

>>> abs.__doc__
'abs(number) -> number

Return the absolute value of the argument.

Syntax

Python has no mandatory statement termination characters and blocks are specified by indentation. Indent to begin a block, dedent to end one. Statements that expect an indentation level end in a colon (:). Comments start with the pound (#) sign and are single-line, multi-line strings are used for multi-line commentsValues are assigned (in fact, objects are bound to names) with the equals sign (“=”), and equality testing is done using two equals signs (“==”). You can increment/decrement values using the += and -= operators respectively by the right-hand amount. This works on many datatypes, strings included. You can also use multiple variables on one line. For example:

>>> myvar = 3
>>> myvar += 2
>>> myvar
5
>>> myvar -= 1
>>> myvar
4
"""This is a multiline comment.
The following lines concatenate the two strings."""
>>> mystring = "Hello"
>>> mystring += " world."
>>> print(mystring)
Hello world.
# This swaps the variables in one line(!).
# It doesn't violate strong typing because values aren't
# actually being assigned, but new objects are bound to
# the old names.
>>> myvar, mystring = mystring, myvar

Data types

The data structures available in python are lists, tuples and dictionaries. Sets are available in the sets library (but are built-in in Python 2.5 and later). Lists are like one-dimensional arrays (but you can also have lists of other lists), dictionaries are associative arrays (a.k.a. hash tables) and tuples are immutable one-dimensional arrays (Python “arrays” can be of any type, so you can mix e.g. integers, strings, etc in lists/dictionaries/tuples). The index of the first item in all array types is 0. Negative numbers count from the end towards the beginning, -1 is the last item. Variables can point to functions. The usage is as follows:

>>> sample = [1, ["another", "list"], ("a", "tuple")]
>>> mylist = ["List item 1", 2, 3.14]
>>> mylist[0] = "List item 1 again" # We're changing the item.
>>> mylist[-1] = 3.21 # Here, we refer to the last item.
>>> mydict = {"Key 1": "Value 1", 2: 3, "pi": 3.14}
>>> mydict["pi"] = 3.15 # This is how you change dictionary values.
>>> mytuple = (1, 2, 3)
>>> myfunction = len
>>> print(myfunction(mylist))
3

You can access array ranges using a colon (:). Leaving the start index empty assumes the first item, leaving the end index assumes the last item. Indexing is inclusive-exclusive, so specifying [2:10] will return items [2] (the third item, because of 0-indexing) to [9] (the tenth item), inclusive (8 items). Negative indexes count from the last item backwards (thus -1 is the last item) like so:

>>> mylist = ["List item 1", 2, 3.14]
>>> print(mylist[:])
['List item 1', 2, 3.1400000000000001]
>>> print(mylist[0:2])
['List item 1', 2]
>>> print(mylist[-3:-1])
['List item 1', 2]
>>> print(mylist[1:])
[2, 3.14]
# Adding a third parameter, "step" will have Python step in
# N item increments, rather than 1.
# E.g., this will return the first item, then go to the third and
# return that (so, items 0 and 2 in 0-indexing).
>>> print(mylist[::2])
['List item 1', 3.14]

Strings

Its strings can use either single or double quotation marks, and you can have quotation marks of one kind inside a string that uses the other kind (i.e. “He said ’hello’.” is valid). Multiline strings are enclosed in _triple double (or single) quotes_ (“”“). Python strings are always Unicode, but there is another string type that is pure bytes. Those are called bytestrings and are represented with the b prefix, for example b'Hello \xce\xb1'. . To fill a string with values, you use the % (modulo) operator and a tuple. Each %s gets replaced with an item from the tuple, left to right, and you can also use dictionary substitutions, like so:

>>> print("Name: %s\
Number: %s\
String: %s" % (myclass.name, 3, 3 * "-"))
Name: Stavros
Number: 3
String: ---

strString = """This is
a multiline
string."""

# WARNING: Watch out for the trailing s in "%(key)s".
>>> print("This %(verb)s a %(noun)s." % {"noun": "test", "verb": "is"})
This is a test.

>>> name = "Stavros"
>>> "Hello, {}!".format(name)
Hello, Stavros!
>>> print(f"Hello, {name}!")
Hello, Stavros!

Flow control statements

Flow control statements are iffor, and while. There is no switch; instead, use if. Use for to enumerate through members of a list. To obtain a sequence of numbers you can iterate over, use range(<number>). These statements’ syntax is thus:

>>> print(range(10))
range(0, 10)
>>> rangelist = list(range(10))
>>> print(rangelist)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for number in range(10):
    # Check if number is one of
    # the numbers in the tuple.
    if number in (3, 4, 7, 9):
        # "Break" terminates a for without
        # executing the "else" clause.
        break
    else:
        # "Continue" starts the next iteration
        # of the loop. It's rather useless here,
        # as it's the last statement of the loop.
        continue
else:
    # The "else" clause is optional and is
    # executed only if the loop didn't "break".
    pass # Do nothing

if rangelist[1] == 2:
    print("The second item (lists are 0-based) is 2")
elif rangelist[1] == 3:
    print("The second item (lists are 0-based) is 3")
else:
    print("Dunno")

while rangelist[1] == 1:
    print("We are trapped in an infinite loop!")

Functions

Functions are declared with the def keyword. Optional arguments are set in the function declaration after the mandatory arguments by being assigned a default value. For named arguments, the name of the argument is assigned a value. Functions can return a tuple (and using tuple unpacking you can effectively return multiple values). Lambda functions are ad hoc functions that are comprised of a single statement. Parameters are passed by reference, but immutable types (tuples, ints, strings, etc) cannot be changed in the caller by the callee. This is because only the memory location of the item is passed, and binding another object to a variable discards the old one, so immutable types are replaced. For example:

# Same as def funcvar(x): return x + 1
funcvar = lambda x: x + 1
>>> print(funcvar(1))
2

# an_int and a_string are optional, they have default values
# if one is not passed (2 and "A default string", respectively).
def passing_example(a_list, an_int=2, a_string="A default string"):
    a_list.append("A new item")
    an_int = 4
    return a_list, an_int, a_string

>>> my_list = [1, 2, 3]
>>> my_int = 10
>>> print(passing_example(my_list, my_int))
([1, 2, 3, 'A new item'], 4, "A default string")
>>> my_list
[1, 2, 3, 'A new item']
>>> my_int
10

Classes

Python supports a limited form of multiple inheritance in classes. Private variables and methods can be declared (by convention, this is not enforced by the language) by adding a leading underscore (e.g. _spam). We can also bind arbitrary names to class instances. An example follows:

class MyClass(object):
    common = 10
    def __init__(self):
        self.myvariable = 3
    def myfunction(self, arg1, arg2):
        return self.myvariable

    # This is the class instantiation

>>> classinstance = MyClass()
>>> classinstance.myfunction(1, 2)
3
# This variable is shared by all instances.
>>> classinstance2 = MyClass()
>>> classinstance.common
10
>>> classinstance2.common
10
# Note how we use the class name
# instead of the instance.
>>> MyClass.common = 30
>>> classinstance.common
30
>>> classinstance2.common
30
# This will not update the variable on the class,
# instead it will bind a new object to the old
# variable name.
>>> classinstance.common = 10
>>> classinstance.common
10
>>> classinstance2.common
30
>>> MyClass.common = 50
# This has not changed, because "common" is
# now an instance variable.
>>> classinstance.common
10
>>> classinstance2.common
50

# This class inherits from MyClass. The example
# class above inherits from "object", which makes
# it what's called a "new-style class".
# Multiple inheritance is declared as:
# class OtherClass(MyClass1, MyClass2, MyClassN)
class OtherClass(MyClass):
    # The "self" argument is passed automatically
    # and refers to the class instance, so you can set
    # instance variables as above, but from inside the class.
    def __init__(self, arg1):
        self.myvariable = 3
        print(arg1)

>>> classinstance = OtherClass("hello")
hello
>>> classinstance.myfunction(1, 2)
3
# This class doesn't have a .test member, but
# we can add one to the instance anyway. Note
# that this will only be a member of classinstance.
>>> classinstance.test = 10
>>> classinstance.test
10

Exceptions

Exceptions in Python are handled with try-except [exceptionname] blocks:

def some_function():
    try:
        # Division by zero raises an exception
        10 / 0
    except ZeroDivisionError:
        print("Oops, invalid.")
    else:
        # Exception didn't occur, we're good.
        pass
    finally:
        # This is executed after the code block is run
        # and all exceptions have been handled, even
        # if a new exception is raised while handling.
        print("We're done with that.")

>>> some_function()
Oops, invalid.
We're done with that.

Importing

External libraries are used with the import [libname] keyword. You can also use from [libname] import [funcname] for individual functions. Here is an example:

import random
from time import clock

randomint = random.randint(1, 100)
>>> print(randomint)
64

File I/O

Python has a wide array of libraries built in. As an example, here is how serializing (converting data structures to strings using the pickle library) with file I/O is used:

import pickle
mylist = ["This", "is", 4, 13327]
# Open the file C:\\binary.dat for writing. The letter r before the
# filename string is used to prevent backslash escaping.
myfile = open(r"C:\\binary.dat", "wb")
pickle.dump(mylist, myfile)
myfile.close()

myfile = open(r"C:\\text.txt", "w")
myfile.write("This is a sample string")
myfile.close()

myfile = open(r"C:\\text.txt")
>>> print(myfile.read())
'This is a sample string'
myfile.close()

# Open the file for reading.
myfile = open(r"C:\\binary.dat", "rb")
loadedlist = pickle.load(myfile)
myfile.close()
>>> print(loadedlist)
['This', 'is', 4, 13327]

Miscellaneous

  • Conditions can be chained1 < a < 3 checks that a is both less than 3 and greater than 1.
  • You can use del to delete variables or items in arrays.
  • List comprehensions provide a powerful way to create and manipulate lists. They consist of an expression followed by a for clause followed by zero or more if or for clauses, like so:
>>> lst1 = [1, 2, 3]
>>> lst2 = [3, 4, 5]
>>> print([x * y for x in lst1 for y in lst2])
[3, 4, 5, 6, 8, 10, 9, 12, 15]
>>> print([x for x in lst1 if 4 > x > 1])
[2, 3]
# Check if a condition is true for any items.
# "any" returns true if any item in the list is true.
>>> any([i % 3 for i in [3, 3, 4, 4, 3]])
True
# This is because 4 % 3 = 1, and 1 is true, so any()
# returns True.

# Check for how many items a condition is true.
>>> sum(1 for i in [3, 3, 4, 4, 3] if i == 4)
2
>>> del lst1[0]
>>> print(lst1)
[2, 3]
>>> del lst1
  • Global variables are declared outside of functions and can be read without any special declarations, but if you want to write to them you must declare them at the beginning of the function with the global keyword, otherwise Python will bind that object to a new local variable (be careful of that, it’s a small catch that can get you if you don’t know it). For example:
number = 5

def myfunc():
    # This will print 5.
    print(number)

def anotherfunc():
    # This raises an exception because the variable has not
    # been bound before printing. Python knows that it an
    # object will be bound to it later and creates a new, local
    # object instead of accessing the global one.
    print(number)
    number = 3

def yetanotherfunc():
    global number
    # This will correctly change the global.
    number = 3

Epilogue

This tutorial is not meant to be an exhaustive list of all (or even a subset) of Python. Python has a vast array of libraries and much much more functionality which you will have to discover through other means, such as the excellent book Dive into Python. I hope I have made your transition in Python easier. Please leave comments if you believe there is something that could be improved or added or if there is anything else you would like to see (classes, error handling, anything).